Skip to main content

diffsol_c/
ode_diffsl_c.rs

1//! The C API constructors for DiffSL equations: JIT-compiled code, and external (static or
2//! dynamic library) symbols.
3
4#[cfg(any(
5    feature = "diffsl-external-dynamic",
6    feature = "diffsl-cranelift",
7    feature = "diffsl-llvm",
8))]
9use std::ffi::CStr;
10#[cfg(any(
11    feature = "diffsl-external-dynamic",
12    feature = "diffsl-cranelift",
13    feature = "diffsl-llvm",
14))]
15use std::os::raw::c_char;
16#[cfg(feature = "diffsl-external-dynamic")]
17use std::path::PathBuf;
18use std::ptr;
19
20use crate::c_error;
21#[cfg(any(
22    feature = "diffsl-external-dynamic",
23    feature = "diffsl-cranelift",
24    feature = "diffsl-llvm",
25))]
26use crate::c_invalid_arg;
27#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
28use crate::jit_c::jit_backend_from_i32;
29use crate::ode::OdeWrapper;
30use crate::ode_c::parse_ode_new_common_args;
31use crate::scalar_type::ScalarType;
32
33#[cfg(any(feature = "external", feature = "diffsl-external-dynamic"))]
34#[repr(C)]
35#[derive(Clone, Copy, Debug)]
36/// C-compatible dependency pair used by the FFI constructors.
37///
38/// Memory layout guarantees:
39/// - `#[repr(C)]` preserves field order exactly as declared.
40/// - The first machine word is `row`, followed by `col`.
41/// - Each field is a `usize`, so size/alignment are target-dependent
42///   (32-bit targets: 4 bytes, 64-bit targets: 8 bytes).
43/// - Arrays of `DiffsolDepPair` are contiguous in memory and can be passed as
44///   pointer/length (`*const DiffsolDepPair`, `usize`) to this API.
45pub struct DiffsolDepPair {
46    pub row: usize,
47    pub col: usize,
48}
49
50#[cfg(any(feature = "external", feature = "diffsl-external-dynamic"))]
51unsafe fn dependency_pairs_from_raw_parts(
52    deps_ptr: *const DiffsolDepPair,
53    deps_len: usize,
54) -> Vec<(usize, usize)> {
55    if deps_ptr.is_null() || deps_len == 0 {
56        Vec::new()
57    } else {
58        unsafe { std::slice::from_raw_parts(deps_ptr, deps_len) }
59            .iter()
60            .map(|pair| (pair.row, pair.col))
61            .collect()
62    }
63}
64
65#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
66fn parse_ode_new_jit_args(
67    code: *const c_char,
68    matrix_type: i32,
69    linear_solver: i32,
70    ode_solver: i32,
71) -> Option<(
72    String,
73    crate::matrix_type::MatrixType,
74    crate::linear_solver_type::LinearSolverType,
75    crate::ode_solver_type::OdeSolverType,
76)> {
77    if code.is_null() {
78        c_invalid_arg!("code is null");
79        return None;
80    }
81    let code = unsafe { CStr::from_ptr(code) };
82    let code = match code.to_str() {
83        Ok(value) => value.to_owned(),
84        Err(_) => {
85            c_error!("code is not valid UTF-8");
86            return None;
87        }
88    };
89    let (matrix_type, linear_solver, ode_solver) =
90        parse_ode_new_common_args(matrix_type, linear_solver, ode_solver)?;
91    Some((code, matrix_type, linear_solver, ode_solver))
92}
93
94#[cfg(feature = "diffsl-external-dynamic")]
95fn parse_ode_new_external_dynamic_args(
96    path: *const c_char,
97    matrix_type: i32,
98    linear_solver: i32,
99    ode_solver: i32,
100) -> Option<(
101    PathBuf,
102    crate::matrix_type::MatrixType,
103    crate::linear_solver_type::LinearSolverType,
104    crate::ode_solver_type::OdeSolverType,
105)> {
106    if path.is_null() {
107        c_invalid_arg!("path is null");
108        return None;
109    }
110    let path = unsafe { CStr::from_ptr(path) };
111    let path = match path.to_str() {
112        Ok(value) => PathBuf::from(value),
113        Err(_) => {
114            c_error!("path is not valid UTF-8");
115            return None;
116        }
117    };
118    let (matrix_type, linear_solver, ode_solver) =
119        parse_ode_new_common_args(matrix_type, linear_solver, ode_solver)?;
120    Some((path, matrix_type, linear_solver, ode_solver))
121}
122
123#[cfg(feature = "external")]
124/// Construct an external-backed ODE wrapper.
125///
126/// # Safety
127/// Dependency pointers must be either null with length `0` or point to valid
128/// memory containing [`DiffsolDepPair`] values for the specified lengths for the
129/// duration of this call.
130#[unsafe(no_mangle)]
131pub unsafe extern "C" fn diffsol_ode_new_external(
132    matrix_type: i32,
133    linear_solver: i32,
134    ode_solver: i32,
135    rhs_state_deps_ptr: *const DiffsolDepPair,
136    rhs_state_deps_len: usize,
137    rhs_input_deps_ptr: *const DiffsolDepPair,
138    rhs_input_deps_len: usize,
139    mass_state_deps_ptr: *const DiffsolDepPair,
140    mass_state_deps_len: usize,
141) -> *mut OdeWrapper {
142    let Some((matrix_type, linear_solver, ode_solver)) =
143        parse_ode_new_common_args(matrix_type, linear_solver, ode_solver)
144    else {
145        return ptr::null_mut();
146    };
147
148    let rhs_state_deps =
149        unsafe { dependency_pairs_from_raw_parts(rhs_state_deps_ptr, rhs_state_deps_len) };
150    let rhs_input_deps =
151        unsafe { dependency_pairs_from_raw_parts(rhs_input_deps_ptr, rhs_input_deps_len) };
152    let mass_state_deps =
153        unsafe { dependency_pairs_from_raw_parts(mass_state_deps_ptr, mass_state_deps_len) };
154
155    let scalar_type = ScalarType::F64;
156    match OdeWrapper::new_external(
157        rhs_state_deps,
158        rhs_input_deps,
159        mass_state_deps,
160        scalar_type,
161        matrix_type,
162        linear_solver,
163        ode_solver,
164    ) {
165        Ok(ode) => Box::into_raw(Box::new(ode)),
166        Err(err) => {
167            c_error!(&format!("{}", err));
168            ptr::null_mut()
169        }
170    }
171}
172
173#[cfg(feature = "diffsl-external-dynamic")]
174/// Construct a dynamic-library-backed ODE wrapper.
175///
176/// # Safety
177/// `path` must be a valid, null-terminated UTF-8 string for the duration of
178/// this call. Dependency pointers must be either null with length `0` or point
179/// to valid memory containing [`DiffsolDepPair`] values for the specified lengths
180/// for the duration of this call.
181#[unsafe(no_mangle)]
182pub unsafe extern "C" fn diffsol_ode_new_external_dynamic(
183    path: *const c_char,
184    matrix_type: i32,
185    linear_solver: i32,
186    ode_solver: i32,
187    rhs_state_deps_ptr: *const DiffsolDepPair,
188    rhs_state_deps_len: usize,
189    rhs_input_deps_ptr: *const DiffsolDepPair,
190    rhs_input_deps_len: usize,
191    mass_state_deps_ptr: *const DiffsolDepPair,
192    mass_state_deps_len: usize,
193) -> *mut OdeWrapper {
194    let Some((path, matrix_type, linear_solver, ode_solver)) =
195        parse_ode_new_external_dynamic_args(path, matrix_type, linear_solver, ode_solver)
196    else {
197        return ptr::null_mut();
198    };
199
200    let rhs_state_deps =
201        unsafe { dependency_pairs_from_raw_parts(rhs_state_deps_ptr, rhs_state_deps_len) };
202    let rhs_input_deps =
203        unsafe { dependency_pairs_from_raw_parts(rhs_input_deps_ptr, rhs_input_deps_len) };
204    let mass_state_deps =
205        unsafe { dependency_pairs_from_raw_parts(mass_state_deps_ptr, mass_state_deps_len) };
206
207    let scalar_type = ScalarType::F64;
208    match OdeWrapper::new_external_dynamic(
209        path,
210        rhs_state_deps,
211        rhs_input_deps,
212        mass_state_deps,
213        scalar_type,
214        matrix_type,
215        linear_solver,
216        ode_solver,
217    ) {
218        Ok(ode) => Box::into_raw(Box::new(ode)),
219        Err(err) => {
220            c_error!(&format!("{}", err));
221            ptr::null_mut()
222        }
223    }
224}
225
226#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
227/// Construct a JIT-backed ODE wrapper from DiffSL source code.
228///
229/// # Safety
230/// `code` must be a valid, null-terminated UTF-8 string for the duration of
231/// this call. The backend and solver enum values must be valid values defined by
232/// this library.
233#[unsafe(no_mangle)]
234pub unsafe extern "C" fn diffsol_ode_new_jit(
235    code: *const c_char,
236    jit_backend: i32,
237    matrix_type: i32,
238    linear_solver: i32,
239    ode_solver: i32,
240) -> *mut OdeWrapper {
241    let Some((code, matrix_type, linear_solver, ode_solver)) =
242        parse_ode_new_jit_args(code, matrix_type, linear_solver, ode_solver)
243    else {
244        return ptr::null_mut();
245    };
246    let jit_backend = match jit_backend_from_i32(jit_backend) {
247        Some(value) => value,
248        None => {
249            c_invalid_arg!("invalid jit_backend_type");
250            return ptr::null_mut();
251        }
252    };
253    let scalar_type = ScalarType::F64;
254    match OdeWrapper::new_jit(
255        &code,
256        jit_backend,
257        scalar_type,
258        matrix_type,
259        linear_solver,
260        ode_solver,
261    ) {
262        Ok(ode) => Box::into_raw(Box::new(ode)),
263        Err(err) => {
264            c_error!(&format!("{}", err));
265            ptr::null_mut()
266        }
267    }
268}