use crate::symbolic::codegen::codegen_aot_registry::RegisteredAotArtifact;
use libloading::Library;
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::{Arc, Mutex, OnceLock};
pub type LinkedResidualEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
pub type LinkedDenseJacobianEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
pub type LinkedSparseJacobianEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
pub type LinkedResidualChunkEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
pub type LinkedDenseJacobianChunkEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
pub type LinkedSparseJacobianChunkEval = dyn Fn(&[f64], &mut [f64]) + Send + Sync;
type AbiWholeEval = unsafe extern "C" fn(*const f64, usize, *mut f64, usize) -> bool;
struct LoadedSparseCdylib {
_library: Library,
residual_eval: AbiWholeEval,
jacobian_values_eval: AbiWholeEval,
}
struct LoadedDenseCdylib {
_library: Library,
residual_eval: AbiWholeEval,
jacobian_eval: AbiWholeEval,
}
struct LoadedResidualCdylib {
_library: Library,
residual_eval: AbiWholeEval,
}
#[derive(Clone)]
pub struct LinkedResidualChunk {
pub output_offset: usize,
pub output_len: usize,
pub eval: Arc<LinkedResidualChunkEval>,
}
impl LinkedResidualChunk {
pub fn new(
output_offset: usize,
output_len: usize,
eval: Arc<LinkedResidualChunkEval>,
) -> Self {
Self {
output_offset,
output_len,
eval,
}
}
}
#[derive(Clone)]
pub struct LinkedDenseJacobianChunk {
pub value_offset: usize,
pub value_len: usize,
pub eval: Arc<LinkedDenseJacobianChunkEval>,
}
impl LinkedDenseJacobianChunk {
pub fn new(
value_offset: usize,
value_len: usize,
eval: Arc<LinkedDenseJacobianChunkEval>,
) -> Self {
Self {
value_offset,
value_len,
eval,
}
}
}
#[derive(Clone)]
pub struct LinkedSparseJacobianChunk {
pub value_offset: usize,
pub value_len: usize,
pub eval: Arc<LinkedSparseJacobianChunkEval>,
}
impl LinkedSparseJacobianChunk {
pub fn new(
value_offset: usize,
value_len: usize,
eval: Arc<LinkedSparseJacobianChunkEval>,
) -> Self {
Self {
value_offset,
value_len,
eval,
}
}
}
#[derive(Clone)]
pub struct LinkedDenseAotBackend {
pub problem_key: String,
pub residual_len: usize,
pub shape: (usize, usize),
pub residual_eval: Arc<LinkedResidualEval>,
pub jacobian_eval: Arc<LinkedDenseJacobianEval>,
pub residual_chunks: Vec<LinkedResidualChunk>,
pub jacobian_chunks: Vec<LinkedDenseJacobianChunk>,
}
impl LinkedDenseAotBackend {
pub fn new(
problem_key: impl Into<String>,
residual_len: usize,
shape: (usize, usize),
residual_eval: Arc<LinkedResidualEval>,
jacobian_eval: Arc<LinkedDenseJacobianEval>,
) -> Self {
Self {
problem_key: problem_key.into(),
residual_len,
shape,
residual_eval,
jacobian_eval,
residual_chunks: Vec::new(),
jacobian_chunks: Vec::new(),
}
}
pub fn with_chunked_evaluators(
mut self,
residual_chunks: Vec<LinkedResidualChunk>,
jacobian_chunks: Vec<LinkedDenseJacobianChunk>,
) -> Self {
self.residual_chunks = residual_chunks;
self.jacobian_chunks = jacobian_chunks;
self
}
}
#[derive(Clone)]
pub struct LinkedResidualAotBackend {
pub problem_key: String,
pub residual_len: usize,
pub residual_eval: Arc<LinkedResidualEval>,
pub residual_chunks: Vec<LinkedResidualChunk>,
}
impl LinkedResidualAotBackend {
pub fn new(
problem_key: impl Into<String>,
residual_len: usize,
residual_eval: Arc<LinkedResidualEval>,
) -> Self {
Self {
problem_key: problem_key.into(),
residual_len,
residual_eval,
residual_chunks: Vec::new(),
}
}
pub fn with_chunked_evaluators(mut self, residual_chunks: Vec<LinkedResidualChunk>) -> Self {
self.residual_chunks = residual_chunks;
self
}
}
#[derive(Clone)]
pub struct LinkedSparseAotBackend {
pub problem_key: String,
pub residual_len: usize,
pub shape: (usize, usize),
pub nnz: usize,
pub residual_eval: Arc<LinkedResidualEval>,
pub jacobian_values_eval: Arc<LinkedSparseJacobianEval>,
pub residual_chunks: Vec<LinkedResidualChunk>,
pub jacobian_value_chunks: Vec<LinkedSparseJacobianChunk>,
}
impl LinkedSparseAotBackend {
pub fn new(
problem_key: impl Into<String>,
residual_len: usize,
shape: (usize, usize),
nnz: usize,
residual_eval: Arc<LinkedResidualEval>,
jacobian_values_eval: Arc<LinkedSparseJacobianEval>,
) -> Self {
Self {
problem_key: problem_key.into(),
residual_len,
shape,
nnz,
residual_eval,
jacobian_values_eval,
residual_chunks: Vec::new(),
jacobian_value_chunks: Vec::new(),
}
}
pub fn with_chunked_evaluators(
mut self,
residual_chunks: Vec<LinkedResidualChunk>,
jacobian_value_chunks: Vec<LinkedSparseJacobianChunk>,
) -> Self {
self.residual_chunks = residual_chunks;
self.jacobian_value_chunks = jacobian_value_chunks;
self
}
}
fn linked_sparse_registry() -> &'static Mutex<BTreeMap<String, LinkedSparseAotBackend>> {
static REGISTRY: OnceLock<Mutex<BTreeMap<String, LinkedSparseAotBackend>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
}
fn linked_residual_registry() -> &'static Mutex<BTreeMap<String, LinkedResidualAotBackend>> {
static REGISTRY: OnceLock<Mutex<BTreeMap<String, LinkedResidualAotBackend>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
}
fn linked_dense_registry() -> &'static Mutex<BTreeMap<String, LinkedDenseAotBackend>> {
static REGISTRY: OnceLock<Mutex<BTreeMap<String, LinkedDenseAotBackend>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
}
pub fn register_linked_dense_backend(backend: LinkedDenseAotBackend) {
linked_dense_registry()
.lock()
.expect("linked dense AOT registry lock poisoned")
.insert(backend.problem_key.clone(), backend);
}
pub fn resolve_linked_dense_backend(problem_key: &str) -> Option<LinkedDenseAotBackend> {
linked_dense_registry()
.lock()
.expect("linked dense AOT registry lock poisoned")
.get(problem_key)
.cloned()
}
pub fn unregister_linked_dense_backend(problem_key: &str) -> Option<LinkedDenseAotBackend> {
linked_dense_registry()
.lock()
.expect("linked dense AOT registry lock poisoned")
.remove(problem_key)
}
pub fn register_linked_residual_backend(backend: LinkedResidualAotBackend) {
linked_residual_registry()
.lock()
.expect("linked residual AOT registry lock poisoned")
.insert(backend.problem_key.clone(), backend);
}
pub fn resolve_linked_residual_backend(problem_key: &str) -> Option<LinkedResidualAotBackend> {
linked_residual_registry()
.lock()
.expect("linked residual AOT registry lock poisoned")
.get(problem_key)
.cloned()
}
pub fn unregister_linked_residual_backend(problem_key: &str) -> Option<LinkedResidualAotBackend> {
linked_residual_registry()
.lock()
.expect("linked residual AOT registry lock poisoned")
.remove(problem_key)
}
fn load_residual_cdylib(path: &Path) -> Result<Arc<LoadedResidualCdylib>, String> {
let library = unsafe { Library::new(path) }
.map_err(|err| format!("failed to load cdylib '{}': {err}", path.display()))?;
let residual_eval = unsafe {
*library
.get::<AbiWholeEval>(b"rustedscithe_aot_eval_residual")
.map_err(|err| {
format!(
"failed to resolve symbol rustedscithe_aot_eval_residual from '{}': {err}",
path.display()
)
})?
};
Ok(Arc::new(LoadedResidualCdylib {
_library: library,
residual_eval,
}))
}
pub fn register_linked_sparse_backend(backend: LinkedSparseAotBackend) {
linked_sparse_registry()
.lock()
.expect("linked sparse AOT registry lock poisoned")
.insert(backend.problem_key.clone(), backend);
}
pub fn resolve_linked_sparse_backend(problem_key: &str) -> Option<LinkedSparseAotBackend> {
linked_sparse_registry()
.lock()
.expect("linked sparse AOT registry lock poisoned")
.get(problem_key)
.cloned()
}
pub fn unregister_linked_sparse_backend(problem_key: &str) -> Option<LinkedSparseAotBackend> {
linked_sparse_registry()
.lock()
.expect("linked sparse AOT registry lock poisoned")
.remove(problem_key)
}
fn load_sparse_cdylib(path: &Path) -> Result<Arc<LoadedSparseCdylib>, String> {
let library = unsafe { Library::new(path) }
.map_err(|err| format!("failed to load cdylib '{}': {err}", path.display()))?;
let residual_eval = unsafe {
*library
.get::<AbiWholeEval>(b"rustedscithe_aot_eval_residual")
.map_err(|err| {
format!(
"failed to resolve symbol rustedscithe_aot_eval_residual from '{}': {err}",
path.display()
)
})?
};
let jacobian_values_eval = unsafe {
*library
.get::<AbiWholeEval>(b"rustedscithe_aot_eval_jacobian_values")
.map_err(|err| {
format!(
"failed to resolve symbol rustedscithe_aot_eval_jacobian_values from '{}': {err}",
path.display()
)
})?
};
Ok(Arc::new(LoadedSparseCdylib {
_library: library,
residual_eval,
jacobian_values_eval,
}))
}
fn load_dense_cdylib(path: &Path) -> Result<Arc<LoadedDenseCdylib>, String> {
let library = unsafe { Library::new(path) }
.map_err(|err| format!("failed to load cdylib '{}': {err}", path.display()))?;
let residual_eval = unsafe {
*library
.get::<AbiWholeEval>(b"rustedscithe_aot_eval_residual")
.map_err(|err| {
format!(
"failed to resolve symbol rustedscithe_aot_eval_residual from '{}': {err}",
path.display()
)
})?
};
let jacobian_eval = unsafe {
*library
.get::<AbiWholeEval>(b"rustedscithe_aot_eval_jacobian_values")
.map_err(|err| {
format!(
"failed to resolve symbol rustedscithe_aot_eval_jacobian_values from '{}': {err}",
path.display()
)
})?
};
Ok(Arc::new(LoadedDenseCdylib {
_library: library,
residual_eval,
jacobian_eval,
}))
}
pub fn register_generated_sparse_cdylib_backend(
artifact: &RegisteredAotArtifact,
) -> Result<LinkedSparseAotBackend, String> {
let path = &artifact.expected_cdylib;
if !path.exists() {
return Err(format!(
"compiled sparse cdylib does not exist at '{}'",
path.display()
));
}
let loaded = load_sparse_cdylib(path)?;
let residual_len = artifact.manifest.io.residual_len;
let shape = (
artifact.manifest.io.jacobian_rows,
artifact.manifest.io.jacobian_cols,
);
let nnz = artifact
.manifest
.io
.jacobian_nnz
.unwrap_or(shape.0 * shape.1);
let residual_loaded = Arc::clone(&loaded);
let residual_eval = Arc::new(move |args: &[f64], out: &mut [f64]| {
let ok = unsafe {
(residual_loaded.residual_eval)(args.as_ptr(), args.len(), out.as_mut_ptr(), out.len())
};
assert!(
ok,
"generated sparse cdylib residual callback returned false"
);
});
let jacobian_loaded = Arc::clone(&loaded);
let jacobian_values_eval = Arc::new(move |args: &[f64], out: &mut [f64]| {
let ok = unsafe {
(jacobian_loaded.jacobian_values_eval)(
args.as_ptr(),
args.len(),
out.as_mut_ptr(),
out.len(),
)
};
assert!(
ok,
"generated sparse cdylib jacobian callback returned false"
);
});
let backend = LinkedSparseAotBackend::new(
artifact.problem_key.clone(),
residual_len,
shape,
nnz,
residual_eval,
jacobian_values_eval,
);
register_linked_sparse_backend(backend.clone());
Ok(backend)
}
pub fn register_generated_banded_cdylib_backend(
artifact: &RegisteredAotArtifact,
) -> Result<LinkedSparseAotBackend, String> {
register_generated_sparse_cdylib_backend(artifact)
}
pub fn register_generated_dense_cdylib_backend(
artifact: &RegisteredAotArtifact,
) -> Result<LinkedDenseAotBackend, String> {
let path = &artifact.expected_cdylib;
if !path.exists() {
return Err(format!(
"compiled dense cdylib does not exist at '{}'",
path.display()
));
}
let loaded = load_dense_cdylib(path)?;
let residual_len = artifact.manifest.io.residual_len;
let shape = (
artifact.manifest.io.jacobian_rows,
artifact.manifest.io.jacobian_cols,
);
let residual_loaded = Arc::clone(&loaded);
let residual_eval = Arc::new(move |args: &[f64], out: &mut [f64]| {
let ok = unsafe {
(residual_loaded.residual_eval)(args.as_ptr(), args.len(), out.as_mut_ptr(), out.len())
};
assert!(
ok,
"generated dense cdylib residual callback returned false"
);
});
let jacobian_loaded = Arc::clone(&loaded);
let jacobian_eval = Arc::new(move |args: &[f64], out: &mut [f64]| {
let ok = unsafe {
(jacobian_loaded.jacobian_eval)(args.as_ptr(), args.len(), out.as_mut_ptr(), out.len())
};
assert!(
ok,
"generated dense cdylib jacobian callback returned false"
);
});
let backend = LinkedDenseAotBackend::new(
artifact.problem_key.clone(),
residual_len,
shape,
residual_eval,
jacobian_eval,
);
register_linked_dense_backend(backend.clone());
Ok(backend)
}
pub fn register_generated_residual_cdylib_backend(
artifact: &RegisteredAotArtifact,
) -> Result<LinkedResidualAotBackend, String> {
let path = &artifact.expected_cdylib;
if !path.exists() {
return Err(format!(
"compiled residual cdylib does not exist at '{}'",
path.display()
));
}
let loaded = load_residual_cdylib(path)?;
let residual_len = artifact.manifest.io.residual_len;
let residual_loaded = Arc::clone(&loaded);
let residual_eval = Arc::new(move |args: &[f64], out: &mut [f64]| {
let ok = unsafe {
(residual_loaded.residual_eval)(args.as_ptr(), args.len(), out.as_mut_ptr(), out.len())
};
assert!(ok, "generated residual cdylib callback returned false");
});
let backend =
LinkedResidualAotBackend::new(artifact.problem_key.clone(), residual_len, residual_eval);
register_linked_residual_backend(backend.clone());
Ok(backend)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linked_dense_backend_roundtrip_works() {
let key = "linked_dense_backend_roundtrip";
let backend = LinkedDenseAotBackend::new(
key,
2,
(2, 2),
Arc::new(|args, out| {
out[0] = args[0] + 1.0;
out[1] = args[1] + 2.0;
}),
Arc::new(|args, out| {
out[0] = args[0];
out[1] = args[1];
out[2] = args[0] + args[1];
out[3] = args[0] - args[1];
}),
);
register_linked_dense_backend(backend.clone());
let resolved = resolve_linked_dense_backend(key).expect("backend should be registered");
assert_eq!(resolved.problem_key, key);
assert_eq!(resolved.residual_len, 2);
assert_eq!(resolved.shape, (2, 2));
let mut residual = vec![0.0; 2];
(resolved.residual_eval)(&[3.0, 4.0], &mut residual);
assert_eq!(residual, vec![4.0, 6.0]);
let mut jacobian = vec![0.0; 4];
(resolved.jacobian_eval)(&[3.0, 4.0], &mut jacobian);
assert_eq!(jacobian, vec![3.0, 4.0, 7.0, -1.0]);
unregister_linked_dense_backend(key);
assert!(resolve_linked_dense_backend(key).is_none());
}
#[test]
fn linked_sparse_backend_roundtrip_works() {
let key = "linked_sparse_backend_roundtrip";
let backend = LinkedSparseAotBackend::new(
key,
2,
(2, 2),
2,
Arc::new(|args, out| {
out[0] = args[0] + 1.0;
out[1] = args[1] + 2.0;
}),
Arc::new(|args, out| {
out[0] = args[0];
out[1] = args[1];
}),
);
register_linked_sparse_backend(backend.clone());
let resolved = resolve_linked_sparse_backend(key).expect("backend should be registered");
assert_eq!(resolved.problem_key, key);
assert_eq!(resolved.residual_len, 2);
assert_eq!(resolved.shape, (2, 2));
assert_eq!(resolved.nnz, 2);
let mut residual = vec![0.0; 2];
(resolved.residual_eval)(&[3.0, 4.0], &mut residual);
assert_eq!(residual, vec![4.0, 6.0]);
unregister_linked_sparse_backend(key);
assert!(resolve_linked_sparse_backend(key).is_none());
}
}