#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
use super::types::RssnStatus;
use crate::dag::builder::DagBuilder;
use crate::dag::node::DagNodeId;
use crate::heuristic::{HeuristicConfig, HeuristicEngine, SearchStrategy};
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::panic::catch_unwind;
use std::time::Duration;
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_new() -> *mut DagBuilder {
let result = catch_unwind(|| Box::into_raw(Box::new(DagBuilder::new())));
result.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_free(builder: *mut DagBuilder) {
if builder.is_null() {
return;
}
let _ = catch_unwind(|| {
let _ = unsafe { Box::from_raw(builder) };
});
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_variable(builder: *mut DagBuilder, name: *const c_char) -> u32 {
if builder.is_null() || name.is_null() {
return u32::MAX;
}
let result = catch_unwind(|| -> u32 {
let builder_ref = unsafe { &mut *builder };
let c_str = unsafe { CStr::from_ptr(name) };
builder_ref
.variable_bytes(c_str.to_bytes())
.map_or(u32::MAX, DagNodeId::value)
});
result.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_constant(builder: *mut DagBuilder, val: f64) -> u32 {
if builder.is_null() {
return u32::MAX;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
builder_ref.constant(val).value()
});
result.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_add(builder: *mut DagBuilder, lhs: u32, rhs: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
builder_ref
.add(DagNodeId::new(lhs), DagNodeId::new(rhs))
.value()
});
result.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_simplify(builder: *mut DagBuilder, root: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let config = HeuristicConfig::default();
let mut engine = HeuristicEngine::new(config, SearchStrategy::Greedy);
engine.simplify(builder_ref, root_id).value()
});
result.unwrap_or(u32::MAX)
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile(
builder: *mut DagBuilder,
root: u32,
out_fn: *mut *mut c_void,
) -> RssnStatus {
if builder.is_null() || out_fn.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let ast = crate::ast::convert::dag_to_ast(builder_ref.arena(), root_id);
let ctx_mutex = crate::ffi::jit_context::global_jit_ctx();
let mut ctx = ctx_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ctx.compiler_mut()
.compile(&ast)
.map_or(RssnStatus::CompilationError, |compiled_fn| {
let ptr = compiled_fn as *mut c_void;
unsafe { *out_fn = ptr };
RssnStatus::Success
})
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile(
_builder: *mut DagBuilder,
_root: u32,
_out_fn: *mut *mut c_void,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute(func: *const c_void, variables: *const f64) -> f64 {
if func.is_null() || variables.is_null() {
return 0.0;
}
let result = catch_unwind(|| {
let compiled_fn: crate::jit::compiler::CompiledExprFn =
unsafe { std::mem::transmute(func) };
compiled_fn(variables)
});
result.unwrap_or(0.0)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute(_func: *const c_void, _variables: *const f64) -> f64 {
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_variable_v2(
builder: *mut DagBuilder,
name: *const c_char,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || name.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let c_str = unsafe { CStr::from_ptr(name) };
builder_ref
.variable_bytes(c_str.to_bytes())
.map_or(RssnStatus::InvalidUtf8, |id| {
unsafe { *out_id = id.value() };
RssnStatus::Success
})
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_constant_v2(
builder: *mut DagBuilder,
val: f64,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let id = builder_ref.constant(val);
unsafe { *out_id = id.value() };
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_add_v2(
builder: *mut DagBuilder,
lhs: u32,
rhs: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if lhs == u32::MAX || rhs == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let id = builder_ref.add(DagNodeId::new(lhs), DagNodeId::new(rhs));
unsafe { *out_id = id.value() };
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute_v2(
func: *const c_void,
variables: *const f64,
out_val: *mut f64,
) -> RssnStatus {
if func.is_null() || variables.is_null() || out_val.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| {
let compiled_fn: crate::jit::compiler::CompiledExprFn =
unsafe { std::mem::transmute(func) };
compiled_fn(variables)
});
result.map_or(RssnStatus::Panic, |val| {
unsafe { *out_val = val };
RssnStatus::Success
})
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute_v2(
_func: *const c_void,
_variables: *const f64,
_out_val: *mut f64,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_execute_bulk(
func: *const c_void,
vars_cols: *const *const f64,
n_vars: u32,
n_rows: usize,
out: *mut f64,
) -> RssnStatus {
if func.is_null() || out.is_null() {
return RssnStatus::NullPointer;
}
if n_vars > 0 && vars_cols.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let compiled_fn: crate::jit::compiler::CompiledExprFn =
unsafe { std::mem::transmute(func) };
let nv = n_vars as usize;
let cols: &[*const f64] = unsafe { std::slice::from_raw_parts(vars_cols, nv) };
let out_slice: &mut [f64] = unsafe { std::slice::from_raw_parts_mut(out, n_rows) };
if nv <= 8 {
let mut buf = [0.0f64; 8];
for (row, out_val) in out_slice.iter_mut().enumerate() {
for (vi, &col) in cols.iter().enumerate() {
buf[vi] = unsafe { *col.add(row) };
}
*out_val = compiled_fn(buf.as_ptr());
}
} else {
let mut buf = vec![0.0f64; nv];
for (row, out_val) in out_slice.iter_mut().enumerate() {
for (vi, &col) in cols.iter().enumerate() {
buf[vi] = unsafe { *col.add(row) };
}
*out_val = compiled_fn(buf.as_ptr());
}
}
RssnStatus::Success
}));
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute_bulk(
_func: *const c_void,
_vars_cols: *const *const f64,
_n_vars: u32,
_n_rows: usize,
_out: *mut f64,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_compile_batch(
builder: *mut DagBuilder,
root: u32,
out_fn: *mut *mut c_void,
) -> RssnStatus {
if builder.is_null() || out_fn.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let ast = crate::ast::convert::dag_to_ast(builder_ref.arena(), root_id);
let ctx_mutex = crate::ffi::jit_context::global_jit_ctx();
let mut ctx = ctx_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match ctx.compiler_mut().compile_batch_f64x2(&ast) {
Ok(Some(batch_fn)) => {
unsafe { *out_fn = batch_fn as *mut c_void };
RssnStatus::Success
}
_ => RssnStatus::CompilationError,
}
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_batch(
_builder: *mut DagBuilder,
_root: u32,
_out_fn: *mut *mut c_void,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_execute_batch(
batch_fn: *const c_void,
vars_cols: *const *const f64,
n_rows: usize,
out: *mut f64,
) -> RssnStatus {
if batch_fn.is_null() || vars_cols.is_null() || out.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let f: crate::jit::compiler::CompiledBatchFn = unsafe { std::mem::transmute(batch_fn) };
f(vars_cols, n_rows, out);
RssnStatus::Success
}));
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_execute_batch(
_batch_fn: *const c_void,
_vars_cols: *const *const f64,
_n_rows: usize,
_out: *mut f64,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_simplify_v2(
builder: *mut DagBuilder,
root: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let config = HeuristicConfig::default();
let mut engine = HeuristicEngine::new(config, SearchStrategy::Greedy);
let id = engine.simplify(builder_ref, root_id);
unsafe { *out_id = id.value() };
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_v2(
builder: *mut DagBuilder,
root: u32,
out_fn: *mut *mut c_void,
) -> RssnStatus {
if builder.is_null() || out_fn.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(|| {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let ast = crate::ast::convert::dag_to_ast(builder_ref.arena(), root_id);
let ctx_mutex = crate::ffi::jit_context::global_jit_ctx();
let mut ctx = ctx_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ctx.compiler_mut()
.compile(&ast)
.map_or(RssnStatus::CompilationError, |compiled_fn| {
unsafe { *out_fn = compiled_fn as *mut c_void };
RssnStatus::Success
})
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_v2(
_builder: *mut DagBuilder,
_root: u32,
_out_fn: *mut *mut c_void,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[repr(C)]
pub struct RssnSimplifyConfig {
pub max_depth: u32,
pub timeout_ms: u64,
pub aggressiveness: f64,
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_simplify_with_config(
builder: *mut DagBuilder,
root: u32,
config: *const RssnSimplifyConfig,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let (max_depth, timeout_ms, aggressiveness) = if config.is_null() {
let def = HeuristicConfig::default();
(
def.max_depth,
def.timeout.as_millis() as u64,
def.simplification_aggressiveness,
)
} else {
let c = unsafe { &*config };
(c.max_depth as usize, c.timeout_ms, c.aggressiveness)
};
let result = catch_unwind(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let cfg = HeuristicConfig::default()
.max_depth(max_depth)
.timeout(Duration::from_millis(timeout_ms))
.simplification_aggressiveness(aggressiveness);
let mut engine = HeuristicEngine::new(cfg, SearchStrategy::Greedy);
let id = engine.simplify(builder_ref, root_id);
unsafe { *out_id = id.value() };
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_sub(builder: *mut DagBuilder, lhs: u32, rhs: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.sub(DagNodeId::new(lhs), DagNodeId::new(rhs)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_sub_v2(
builder: *mut DagBuilder,
lhs: u32,
rhs: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if lhs == u32::MAX || rhs == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.sub(DagNodeId::new(lhs), DagNodeId::new(rhs));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_mul(builder: *mut DagBuilder, lhs: u32, rhs: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.mul(DagNodeId::new(lhs), DagNodeId::new(rhs)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_mul_v2(
builder: *mut DagBuilder,
lhs: u32,
rhs: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if lhs == u32::MAX || rhs == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.mul(DagNodeId::new(lhs), DagNodeId::new(rhs));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_div(builder: *mut DagBuilder, lhs: u32, rhs: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.div(DagNodeId::new(lhs), DagNodeId::new(rhs)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_div_v2(
builder: *mut DagBuilder,
lhs: u32,
rhs: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if lhs == u32::MAX || rhs == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.div(DagNodeId::new(lhs), DagNodeId::new(rhs));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_pow(builder: *mut DagBuilder, base: u32, exp: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.pow(DagNodeId::new(base), DagNodeId::new(exp)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_pow_v2(
builder: *mut DagBuilder,
base: u32,
exp: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if base == u32::MAX || exp == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.pow(DagNodeId::new(base), DagNodeId::new(exp));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_mod(builder: *mut DagBuilder, lhs: u32, rhs: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.modulo(DagNodeId::new(lhs), DagNodeId::new(rhs)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_mod_v2(
builder: *mut DagBuilder,
lhs: u32,
rhs: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if lhs == u32::MAX || rhs == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.modulo(DagNodeId::new(lhs), DagNodeId::new(rhs));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_neg(builder: *mut DagBuilder, operand: u32) -> u32 {
if builder.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
b.neg(DagNodeId::new(operand)).value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_neg_v2(
builder: *mut DagBuilder,
operand: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if operand == u32::MAX {
return RssnStatus::InvalidNodeId;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let id = b.neg(DagNodeId::new(operand));
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_parse(
builder: *mut DagBuilder,
expr: *const c_char,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || expr.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let b = unsafe { &mut *builder };
let c_str = unsafe { CStr::from_ptr(expr) };
let Ok(s) = c_str.to_str() else {
return RssnStatus::InvalidUtf8;
};
crate::parser::expr::parse_expression(s, b).map_or(RssnStatus::ParseError, |root_id| {
unsafe { *out_id = root_id.value() };
RssnStatus::Success
})
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_intern_function(builder: *mut DagBuilder, name: *const c_char) -> u32 {
if builder.is_null() || name.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let c_str = unsafe { CStr::from_ptr(name) };
let Ok(s) = c_str.to_str() else {
return u32::MAX;
};
b.intern_function(s).0
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_call_fn(
builder: *mut DagBuilder,
fn_id: u32,
args: *const u32,
n_args: u32,
) -> u32 {
if builder.is_null() {
return u32::MAX;
}
if n_args > 0 && args.is_null() {
return u32::MAX;
}
catch_unwind(|| {
let b = unsafe { &mut *builder };
let arg_ids: Vec<DagNodeId> = if n_args == 0 {
Vec::new()
} else {
let slice = unsafe { std::slice::from_raw_parts(args, n_args as usize) };
if slice.contains(&u32::MAX) {
return u32::MAX;
}
slice.iter().map(|&id| DagNodeId::new(id)).collect()
};
b.function_call(crate::dag::symbol::FnId(fn_id), &arg_ids)
.value()
})
.unwrap_or(u32::MAX)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_call_fn_v2(
builder: *mut DagBuilder,
fn_id: u32,
args: *const u32,
n_args: u32,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if n_args > 0 && args.is_null() {
return RssnStatus::NullPointer;
}
catch_unwind(|| -> RssnStatus {
let b = unsafe { &mut *builder };
let arg_ids: Vec<DagNodeId> = if n_args == 0 {
Vec::new()
} else {
let slice = unsafe { std::slice::from_raw_parts(args, n_args as usize) };
if slice.contains(&u32::MAX) {
return RssnStatus::InvalidNodeId;
}
slice.iter().map(|&id| DagNodeId::new(id)).collect()
};
let id = b.function_call(crate::dag::symbol::FnId(fn_id), &arg_ids);
unsafe { *out_id = id.value() };
RssnStatus::Success
})
.unwrap_or(RssnStatus::Panic)
}
pub type RssnCustomFn1 = extern "C" fn(f64) -> f64;
pub type RssnCustomFn2 = extern "C" fn(f64, f64) -> f64;
pub type RssnCustomFn3 = extern "C" fn(f64, f64, f64) -> f64;
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_1(
ctx: *mut super::jit_context::RssnJitContext,
fn_id: u32,
func: Option<extern "C" fn(f64) -> f64>,
) -> RssnStatus {
if ctx.is_null() {
return RssnStatus::NullPointer;
}
let Some(func_ptr) = func else {
return RssnStatus::NullPointer;
};
catch_unwind(std::panic::AssertUnwindSafe(|| {
let ctx_ref = unsafe { &mut *ctx };
ctx_ref
.compiler_mut()
.register_custom_function(crate::dag::symbol::FnId(fn_id), func_ptr);
RssnStatus::Success
}))
.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_1(
_ctx: *mut super::jit_context::RssnJitContext,
_fn_id: u32,
_func: Option<extern "C" fn(f64) -> f64>,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_2(
ctx: *mut super::jit_context::RssnJitContext,
fn_id: u32,
func: Option<extern "C" fn(f64, f64) -> f64>,
) -> RssnStatus {
if ctx.is_null() {
return RssnStatus::NullPointer;
}
let Some(func_ptr) = func else {
return RssnStatus::NullPointer;
};
catch_unwind(std::panic::AssertUnwindSafe(|| {
let ctx_ref = unsafe { &mut *ctx };
ctx_ref
.compiler_mut()
.register_custom_function_2(crate::dag::symbol::FnId(fn_id), func_ptr);
RssnStatus::Success
}))
.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_2(
_ctx: *mut super::jit_context::RssnJitContext,
_fn_id: u32,
_func: Option<extern "C" fn(f64, f64) -> f64>,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_3(
ctx: *mut super::jit_context::RssnJitContext,
fn_id: u32,
func: Option<extern "C" fn(f64, f64, f64) -> f64>,
) -> RssnStatus {
if ctx.is_null() {
return RssnStatus::NullPointer;
}
let Some(func_ptr) = func else {
return RssnStatus::NullPointer;
};
catch_unwind(std::panic::AssertUnwindSafe(|| {
let ctx_ref = unsafe { &mut *ctx };
ctx_ref
.compiler_mut()
.register_custom_function_3(crate::dag::symbol::FnId(fn_id), func_ptr);
RssnStatus::Success
}))
.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_jit_register_fn_3(
_ctx: *mut super::jit_context::RssnJitContext,
_fn_id: u32,
_func: Option<extern "C" fn(f64, f64, f64) -> f64>,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[cfg(feature = "cranelift-jit")]
#[repr(C)]
pub struct RssnOptConfig {
pub max_int_pow: u32,
pub expand_sqrt: u32,
pub allow_reciprocal_math: u32,
pub elide_nan_guard: u32,
pub enable_cse: u32,
}
#[cfg(not(feature = "cranelift-jit"))]
#[repr(C)]
pub struct RssnOptConfig;
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_with_opts(
ctx: *mut super::jit_context::RssnJitContext,
builder: *mut DagBuilder,
root: u32,
opts: *const RssnOptConfig,
out_fn: *mut *mut c_void,
) -> RssnStatus {
if ctx.is_null() || builder.is_null() || out_fn.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let ctx_ref = unsafe { &mut *ctx };
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let ast = crate::ast::convert::dag_to_ast(builder_ref.arena(), root_id);
let jit_opts = if opts.is_null() {
crate::jit::compiler::OptConfig::default()
} else {
let c = unsafe { &*opts };
crate::jit::compiler::OptConfig {
max_int_pow: c.max_int_pow,
expand_sqrt: c.expand_sqrt != 0,
allow_reciprocal_math: c.allow_reciprocal_math != 0,
elide_nan_guard: c.elide_nan_guard != 0,
enable_cse: c.enable_cse != 0,
}
};
ctx_ref
.compiler_mut()
.compile_with_opts(&ast, &jit_opts)
.map_or(RssnStatus::CompilationError, |compiled_fn| {
unsafe { *out_fn = compiled_fn as *mut c_void };
RssnStatus::Success
})
}));
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_with_opts(
_ctx: *mut super::jit_context::RssnJitContext,
_builder: *mut DagBuilder,
_root: u32,
_opts: *const RssnOptConfig,
_out_fn: *mut *mut c_void,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum RssnKind {
Variable = 0,
Constant = 1,
Add = 2,
Sub = 3,
Mul = 4,
Div = 5,
Pow = 6,
Mod = 7,
Neg = 8,
Function = 9,
}
pub struct RssnRuleRegistry {
inner: std::sync::Arc<crate::heuristic::rule_registry::RuleRegistry>,
}
pub type RssnRuleCallback = unsafe extern "C" fn(
builder: *mut DagBuilder,
kind: u8,
children: *const u32,
n_children: u32,
user_data: *mut c_void,
) -> u32;
#[unsafe(no_mangle)]
pub extern "C" fn rssn_rule_registry_new() -> *mut RssnRuleRegistry {
catch_unwind(|| {
Box::into_raw(Box::new(RssnRuleRegistry {
inner: std::sync::Arc::new(crate::heuristic::rule_registry::RuleRegistry::new()),
}))
})
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_rule_registry_free(registry: *mut RssnRuleRegistry) {
if registry.is_null() {
return;
}
let _ = catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = unsafe { Box::from_raw(registry) };
}));
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_rule_register(
registry: *mut RssnRuleRegistry,
name: *const c_char,
callback: Option<RssnRuleCallback>,
priority: i32,
kind_filter: i32,
user_data: *mut c_void,
) -> RssnStatus {
if registry.is_null() || name.is_null() {
return RssnStatus::NullPointer;
}
let Some(cb) = callback else {
return RssnStatus::NullPointer;
};
let name_str = {
let c_str = unsafe { CStr::from_ptr(name) };
match c_str.to_str() {
Ok(s) => s.to_owned(),
Err(_) => return RssnStatus::InvalidUtf8,
}
};
let user_data_addr = user_data as usize;
let kind_opt = if kind_filter < 0 {
None
} else {
use crate::dag::symbol::{OpKind, SymbolKind};
match kind_filter as u8 {
0 => Some(SymbolKind::Variable(crate::dag::symbol::SymbolId(0))),
1 => Some(SymbolKind::Constant(0.0)),
2 => Some(SymbolKind::Operator(OpKind::Add)),
3 => Some(SymbolKind::Operator(OpKind::Sub)),
4 => Some(SymbolKind::Operator(OpKind::Mul)),
5 => Some(SymbolKind::Operator(OpKind::Div)),
6 => Some(SymbolKind::Operator(OpKind::Pow)),
7 => Some(SymbolKind::Operator(OpKind::Mod)),
8 => Some(SymbolKind::Operator(OpKind::Neg)),
9 => Some(SymbolKind::Function(crate::dag::symbol::FnId(0))),
_ => return RssnStatus::InvalidNodeId,
}
};
catch_unwind(std::panic::AssertUnwindSafe(|| {
let reg_ref = unsafe { &mut *registry };
let Some(inner_mut) = std::sync::Arc::get_mut(&mut reg_ref.inner) else {
return RssnStatus::RuleConflict;
};
inner_mut.register_named(
&name_str,
move |builder, kind, children| {
let child_ids: Vec<u32> = children.iter().map(|id| id.value()).collect();
let user_data_ptr = user_data_addr as *mut c_void;
let result = unsafe {
cb(
std::ptr::from_mut::<DagBuilder>(builder),
kind_to_discriminant(&kind),
child_ids.as_ptr(),
child_ids.len() as u32,
user_data_ptr,
)
};
if result == u32::MAX {
None
} else {
Some(DagNodeId::new(result))
}
},
priority,
kind_opt,
);
RssnStatus::Success
}))
.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_simplify_with_rules(
builder: *mut DagBuilder,
root: u32,
registry: *mut RssnRuleRegistry,
config: *const RssnSimplifyConfig,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let (max_depth, timeout_ms, aggressiveness) = if config.is_null() {
let def = HeuristicConfig::default();
(
def.max_depth,
def.timeout.as_millis() as u64,
def.simplification_aggressiveness,
)
} else {
let c = unsafe { &*config };
(c.max_depth as usize, c.timeout_ms, c.aggressiveness)
};
catch_unwind(std::panic::AssertUnwindSafe(|| -> RssnStatus {
let builder_ref = unsafe { &mut *builder };
let root_id = DagNodeId::new(root);
let cfg = HeuristicConfig::default()
.max_depth(max_depth)
.timeout(Duration::from_millis(timeout_ms))
.simplification_aggressiveness(aggressiveness);
let mut engine = if registry.is_null() {
HeuristicEngine::new(cfg, SearchStrategy::Greedy)
} else {
let arc_clone = std::sync::Arc::clone(unsafe { &(*registry).inner });
HeuristicEngine::new(cfg, SearchStrategy::Greedy).with_rule_registry(arc_clone)
};
let id = engine.simplify(builder_ref, root_id);
unsafe { *out_id = id.value() };
RssnStatus::Success
}))
.unwrap_or(RssnStatus::Panic)
}
const fn kind_to_discriminant(kind: &crate::dag::symbol::SymbolKind) -> u8 {
use crate::dag::symbol::{OpKind, SymbolKind};
match kind {
SymbolKind::Variable(_) => 0,
SymbolKind::Constant(_) => 1,
SymbolKind::Operator(OpKind::Add) => 2,
SymbolKind::Operator(OpKind::Sub) => 3,
SymbolKind::Operator(OpKind::Mul) => 4,
SymbolKind::Operator(OpKind::Div) => 5,
SymbolKind::Operator(OpKind::Pow) => 6,
SymbolKind::Operator(OpKind::Mod) => 7,
SymbolKind::Operator(OpKind::Neg) => 8,
SymbolKind::Function(_) => 9,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binary_ops_roundtrip() {
let builder = rssn_dag_new();
assert!(!builder.is_null());
let x = rssn_dag_variable(builder, c"x".as_ptr());
let y = rssn_dag_variable(builder, c"y".as_ptr());
assert_ne!(x, u32::MAX);
assert_ne!(y, u32::MAX);
assert_ne!(rssn_dag_sub(builder, x, y), u32::MAX);
assert_ne!(rssn_dag_mul(builder, x, y), u32::MAX);
assert_ne!(rssn_dag_div(builder, x, y), u32::MAX);
assert_ne!(rssn_dag_pow(builder, x, y), u32::MAX);
assert_ne!(rssn_dag_mod(builder, x, y), u32::MAX);
assert_ne!(rssn_dag_neg(builder, x), u32::MAX);
rssn_dag_free(builder);
}
#[test]
fn v2_ops_return_success() {
let builder = rssn_dag_new();
let x = rssn_dag_variable(builder, c"x".as_ptr());
let y = rssn_dag_variable(builder, c"y".as_ptr());
let mut out = u32::MAX;
assert_eq!(
rssn_dag_sub_v2(builder, x, y, &mut out),
RssnStatus::Success
);
assert_ne!(out, u32::MAX);
assert_eq!(
rssn_dag_mul_v2(builder, x, y, &mut out),
RssnStatus::Success
);
assert_ne!(out, u32::MAX);
assert_eq!(
rssn_dag_div_v2(builder, x, y, &mut out),
RssnStatus::Success
);
assert_ne!(out, u32::MAX);
assert_eq!(
rssn_dag_pow_v2(builder, x, y, &mut out),
RssnStatus::Success
);
assert_ne!(out, u32::MAX);
assert_eq!(
rssn_dag_mod_v2(builder, x, y, &mut out),
RssnStatus::Success
);
assert_ne!(out, u32::MAX);
assert_eq!(rssn_dag_neg_v2(builder, x, &mut out), RssnStatus::Success);
assert_ne!(out, u32::MAX);
rssn_dag_free(builder);
}
#[test]
fn null_inputs_return_sentinel_or_null_pointer_status() {
assert_eq!(rssn_dag_sub(std::ptr::null_mut(), 0, 0), u32::MAX);
assert_eq!(rssn_dag_mul(std::ptr::null_mut(), 0, 0), u32::MAX);
assert_eq!(rssn_dag_div(std::ptr::null_mut(), 0, 0), u32::MAX);
assert_eq!(rssn_dag_pow(std::ptr::null_mut(), 0, 0), u32::MAX);
assert_eq!(rssn_dag_mod(std::ptr::null_mut(), 0, 0), u32::MAX);
assert_eq!(rssn_dag_neg(std::ptr::null_mut(), 0), u32::MAX);
}
#[test]
fn parse_and_build() {
let builder = rssn_dag_new();
let mut out = u32::MAX;
let status = rssn_dag_parse(builder, c"x + y * 2.0".as_ptr(), &mut out);
assert_eq!(status, RssnStatus::Success);
assert_ne!(out, u32::MAX);
rssn_dag_free(builder);
}
#[test]
fn parse_invalid_expression() {
let builder = rssn_dag_new();
let mut out = u32::MAX;
let status = rssn_dag_parse(builder, c"(".as_ptr(), &mut out);
assert_ne!(status, RssnStatus::Success);
rssn_dag_free(builder);
}
#[test]
fn intern_function_and_call() {
let builder = rssn_dag_new();
let fn_id = rssn_dag_intern_function(builder, c"mysin".as_ptr());
assert_ne!(fn_id, u32::MAX);
let x = rssn_dag_variable(builder, c"x".as_ptr());
let args = [x];
let call_node = rssn_dag_call_fn(builder, fn_id, args.as_ptr(), 1);
assert_ne!(call_node, u32::MAX);
rssn_dag_free(builder);
}
#[test]
fn rule_registry_lifecycle() {
let reg = rssn_rule_registry_new();
assert!(!reg.is_null());
rssn_rule_registry_free(reg);
rssn_rule_registry_free(std::ptr::null_mut());
}
#[test]
fn register_and_apply_c_rule() {
unsafe extern "C" fn zero_add_rule(
builder: *mut DagBuilder,
kind: u8,
children: *const u32,
n_children: u32,
_user_data: *mut c_void,
) -> u32 {
if kind != 2 || n_children != 2 {
return u32::MAX;
}
let lhs = unsafe { *children };
let rhs = unsafe { *children.add(1) };
let b = unsafe { &mut *builder };
let rhs_node = b.arena().get(DagNodeId::new(rhs));
if let Some(node) = rhs_node {
if let crate::dag::symbol::SymbolKind::Constant(v) = node.kind {
if v == 0.0 {
return lhs;
}
}
}
u32::MAX
}
let reg = rssn_rule_registry_new();
let status = rssn_rule_register(
reg,
c"zero_add".as_ptr(),
Some(zero_add_rule),
0,
2, std::ptr::null_mut(),
);
assert_eq!(status, RssnStatus::Success);
let builder = rssn_dag_new();
let x = rssn_dag_variable(builder, c"x".as_ptr());
let zero = rssn_dag_constant(builder, 0.0);
let expr = rssn_dag_add(builder, x, zero);
let mut out = u32::MAX;
let s = rssn_dag_simplify_with_rules(builder, expr, reg, std::ptr::null(), &mut out);
assert_eq!(s, RssnStatus::Success);
assert_eq!(out, x, "x + 0 should simplify to x");
rssn_dag_free(builder);
rssn_rule_registry_free(reg);
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RssnEGraphConfig {
pub max_rounds: u32,
pub max_merges: u32,
pub max_new_nodes: u32,
pub strict_ieee754_signed_zero: u8,
}
impl RssnEGraphConfig {
const fn to_rust(self) -> crate::egraph::EGraphConfig {
crate::egraph::EGraphConfig {
max_rounds: if self.max_rounds == 0 {
8
} else {
self.max_rounds as usize
},
max_merges: if self.max_merges == 0 {
512
} else {
self.max_merges as usize
},
max_new_nodes: if self.max_new_nodes == 0 {
1024
} else {
self.max_new_nodes as usize
},
strict_ieee754_signed_zero: self.strict_ieee754_signed_zero != 0,
cost_weights: None,
}
}
}
pub type RssnEGraphRuleCallback = unsafe extern "C" fn(
builder: *mut DagBuilder,
kind: u8,
children: *const u32,
n_children: u32,
user_data: *mut c_void,
) -> u32;
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_egraph_saturate_extract(
builder: *mut DagBuilder,
root: u32,
cfg: RssnEGraphConfig,
rules: *const RssnEGraphRuleCallback,
n_rules: u32,
out: *mut u32,
) -> RssnStatus {
if builder.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let b = unsafe { &mut *builder };
let root_id = crate::dag::node::DagNodeId::new(root);
let rust_cfg = cfg.to_rust();
let mut eg = crate::egraph::EGraph::new(b, rust_cfg);
if !rules.is_null() {
for i in 0..n_rules as usize {
let cb: RssnEGraphRuleCallback = unsafe { *rules.add(i) };
eg.add_rule(move |builder_inner, kind, children| {
let kind_disc = kind_to_discriminant(kind);
let ch_ptr = children.as_ptr().cast::<u32>();
let result = unsafe {
cb(
std::ptr::from_mut::<DagBuilder>(builder_inner),
kind_disc,
ch_ptr,
children.len() as u32,
std::ptr::null_mut(), )
};
if result == u32::MAX {
None
} else {
Some(crate::dag::node::DagNodeId::new(result))
}
});
}
}
eg.saturate(root_id);
let best = eg.extract(root_id);
if let Some(out_ptr) = unsafe { out.as_mut() } {
*out_ptr = best.value();
}
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_simplify_with_egraph(
builder: *mut DagBuilder,
root: u32,
egraph_cfg: RssnEGraphConfig,
out: *mut u32,
) -> RssnStatus {
if builder.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let b = unsafe { &mut *builder };
let root_id = crate::dag::node::DagNodeId::new(root);
let hcfg = crate::heuristic::HeuristicConfig::default().with_egraph(egraph_cfg.to_rust());
let mut engine = crate::heuristic::HeuristicEngine::new(
hcfg,
crate::heuristic::SearchStrategy::default(),
);
let simplified = engine.simplify(b, root_id);
if let Some(out_ptr) = unsafe { out.as_mut() } {
*out_ptr = simplified.value();
}
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
use std::collections::HashMap as StdHashMap;
use std::sync::OnceLock;
pub type RssnBatchOpCallback = unsafe extern "C" fn(
builder: *mut DagBuilder,
child_ids: *const u32,
n_children: u32,
user_data: *mut c_void,
) -> u32;
struct BatchOpEntry {
callback: RssnBatchOpCallback,
n_children: u32,
user_data: usize,
}
static BATCH_OP_REGISTRY: OnceLock<std::sync::RwLock<StdHashMap<u8, BatchOpEntry>>> =
OnceLock::new();
#[inline]
fn batch_op_registry() -> &'static std::sync::RwLock<StdHashMap<u8, BatchOpEntry>> {
BATCH_OP_REGISTRY.get_or_init(|| std::sync::RwLock::new(StdHashMap::new()))
}
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_batch_op_register(
kind: u8,
n_children: u32,
callback: Option<RssnBatchOpCallback>,
user_data: *mut c_void,
) -> RssnStatus {
if kind < 16 {
return RssnStatus::InvalidNodeId;
}
let Some(cb) = callback else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let reg = batch_op_registry();
let mut guard = reg
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.contains_key(&kind) {
return RssnStatus::RuleConflict;
}
guard.insert(
kind,
BatchOpEntry {
callback: cb,
n_children,
user_data: user_data as usize,
},
);
RssnStatus::Success
}));
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_batch_op_unregister(kind: u8) -> RssnStatus {
if kind < 16 {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let reg = batch_op_registry();
let mut guard = reg
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.remove(&kind).is_some() {
RssnStatus::Success
} else {
RssnStatus::InvalidNodeId
}
}));
result.unwrap_or(RssnStatus::Panic)
}
pub type RssnNodeKindBatch = u8;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RssnNodeDesc {
pub value: f64,
pub child0: u32,
pub child1: u32,
pub kind: u8,
pub name: [u8; 31],
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_batch_build(
builder: *mut crate::dag::builder::DagBuilder,
descs: *const RssnNodeDesc,
n: u32,
out_ids: *mut u32,
) -> RssnStatus {
if builder.is_null() || descs.is_null() || out_ids.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let b = unsafe { &mut *builder };
let descs_slice: &[RssnNodeDesc] = unsafe { std::slice::from_raw_parts(descs, n as usize) };
let out_slice: &mut [u32] = unsafe { std::slice::from_raw_parts_mut(out_ids, n as usize) };
let mut batch_ids: Vec<crate::dag::node::DagNodeId> = Vec::with_capacity(n as usize);
for (i, desc) in descs_slice.iter().enumerate() {
let resolve = |idx: u32| -> crate::dag::node::DagNodeId {
if idx == u32::MAX || idx as usize >= i {
crate::dag::node::DagNodeId::NONE
} else {
batch_ids[idx as usize]
}
};
let id = match desc.kind {
0 => {
let name_bytes = &desc.name;
let len = name_bytes.iter().position(|&b| b == 0).unwrap_or(31);
b.variable_bytes(&name_bytes[..len])
.unwrap_or(crate::dag::node::DagNodeId::NONE)
}
1 => b.constant(desc.value),
2 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.add(c0, c1)
}
3 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.sub(c0, c1)
}
4 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.mul(c0, c1)
}
5 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.div(c0, c1)
}
6 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.pow(c0, c1)
}
7 => {
let c0 = resolve(desc.child0);
if c0.is_none() {
return RssnStatus::InvalidNode;
}
b.neg(c0)
}
8 => {
let (c0, c1) = (resolve(desc.child0), resolve(desc.child1));
if c0.is_none() || c1.is_none() {
return RssnStatus::InvalidNode;
}
b.modulo(c0, c1)
}
kind => {
let entry = {
let reg = batch_op_registry();
let guard = reg
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard
.get(&kind)
.map(|e| (e.callback, e.n_children, e.user_data))
};
let Some((cb, n_ch, ud_usize)) = entry else {
return RssnStatus::InvalidNode;
};
let resolve_raw = |idx: u32| -> u32 {
if idx == u32::MAX || idx as usize >= i {
u32::MAX
} else {
batch_ids[idx as usize].value()
}
};
let actual_n = (n_ch as usize).min(2);
let mut ch_buf = [u32::MAX; 2];
if actual_n > 0 {
ch_buf[0] = resolve_raw(desc.child0);
}
if actual_n > 1 {
ch_buf[1] = resolve_raw(desc.child1);
}
let ud = ud_usize as *mut c_void;
let result_id = unsafe {
cb(
std::ptr::from_mut::<DagBuilder>(b),
ch_buf.as_ptr(),
actual_n as u32,
ud,
)
};
if result_id == u32::MAX {
return RssnStatus::InvalidNode;
}
DagNodeId::new(result_id)
}
};
out_slice[i] = id.value();
batch_ids.push(id);
}
RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_get_packed(
builder: *const crate::dag::builder::DagBuilder,
buf: *mut u8,
buf_len: usize,
bytes_written: *mut usize,
) -> RssnStatus {
if builder.is_null() || bytes_written.is_null() {
return RssnStatus::NullPointer;
}
let result = catch_unwind(|| -> RssnStatus {
let b = unsafe { &*builder };
let image = b.packed_snapshot();
let node_count = image.len();
let pool_count = image.children_pool().len();
let needed = 8 + node_count * 32 + 8 + pool_count * 4;
unsafe {
*bytes_written = needed;
}
if buf.is_null() {
return RssnStatus::Success;
}
if buf_len < needed {
return RssnStatus::BufferTooSmall;
}
let out: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(buf, buf_len) };
let mut pos = 0usize;
out[pos..pos + 8].copy_from_slice(&(node_count as u64).to_le_bytes());
pos += 8;
let node_bytes = image.nodes().len() * 32;
let node_src: &[u8] =
unsafe { std::slice::from_raw_parts(image.nodes().as_ptr().cast::<u8>(), node_bytes) };
out[pos..pos + node_bytes].copy_from_slice(node_src);
pos += node_bytes;
out[pos..pos + 8].copy_from_slice(&(pool_count as u64).to_le_bytes());
pos += 8;
for &v in image.children_pool() {
out[pos..pos + 4].copy_from_slice(&v.to_le_bytes());
pos += 4;
}
let _ = pos; RssnStatus::Success
});
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(test)]
mod egraph_ffi_tests {
use super::*;
#[test]
fn egraph_ffi_constant_fold_add() {
let builder = rssn_dag_new();
let c3 = rssn_dag_constant(builder, 3.0);
let c4 = rssn_dag_constant(builder, 4.0);
let s = rssn_dag_add(builder, c3, c4);
let cfg = RssnEGraphConfig {
max_rounds: 4,
max_merges: 64,
max_new_nodes: 64,
strict_ieee754_signed_zero: 0,
};
let mut out: u32 = u32::MAX;
let status =
rssn_dag_egraph_saturate_extract(builder, s, cfg, std::ptr::null(), 0, &mut out);
assert_eq!(status, RssnStatus::Success);
assert_ne!(out, u32::MAX);
rssn_dag_free(builder);
}
#[test]
fn egraph_ffi_add_zero_simplifies() {
let builder = rssn_dag_new();
let x = rssn_dag_variable(builder, c"x".as_ptr());
let zero = rssn_dag_constant(builder, 0.0);
let xpz = rssn_dag_add(builder, x, zero);
let cfg = RssnEGraphConfig::default();
let mut out: u32 = u32::MAX;
let status =
rssn_dag_egraph_saturate_extract(builder, xpz, cfg, std::ptr::null(), 0, &mut out);
assert_eq!(status, RssnStatus::Success);
assert_eq!(out, x, "x+0 extracts to x");
rssn_dag_free(builder);
}
#[test]
fn egraph_ffi_null_builder_returns_null_pointer() {
let mut out: u32 = 0;
let status = rssn_dag_egraph_saturate_extract(
std::ptr::null_mut(),
0,
RssnEGraphConfig::default(),
std::ptr::null(),
0,
&mut out,
);
assert_eq!(status, RssnStatus::NullPointer);
}
}
#[cfg(test)]
mod batch_build_tests {
use super::*;
#[test]
fn batch_build_polynomial() {
let builder = rssn_dag_new();
let mut out_ids = [u32::MAX; 4];
let mut descs = [RssnNodeDesc {
value: 0.0,
child0: u32::MAX,
child1: u32::MAX,
kind: 0,
name: [0u8; 31],
}; 4];
descs[0].kind = 0;
descs[0].name[0] = b'x';
descs[1].kind = 1;
descs[1].value = 2.0;
descs[2].kind = 2;
descs[2].child0 = 0;
descs[2].child1 = 1;
descs[3].kind = 4;
descs[3].child0 = 0;
descs[3].child1 = 2;
let status = rssn_dag_batch_build(builder, descs.as_ptr(), 4, out_ids.as_mut_ptr());
assert_eq!(status, RssnStatus::Success);
for &id in &out_ids {
assert_ne!(id, u32::MAX, "all nodes should be allocated");
}
assert_eq!(out_ids[0], out_ids[0]);
let x2 = rssn_dag_variable(builder, c"x".as_ptr());
let c2 = rssn_dag_constant(builder, 2.0);
let add2 = rssn_dag_add(builder, x2, c2);
let mul2 = rssn_dag_mul(builder, x2, add2);
assert_eq!(
out_ids[3], mul2,
"batch and individual build produce same node ID"
);
rssn_dag_free(builder);
}
#[test]
fn batch_build_null_returns_null_pointer() {
let mut out_ids = [0u32; 2];
let descs = [RssnNodeDesc {
value: 1.0,
child0: u32::MAX,
child1: u32::MAX,
kind: 1,
name: [0u8; 31],
}; 2];
let status = rssn_dag_batch_build(
std::ptr::null_mut(),
descs.as_ptr(),
2,
out_ids.as_mut_ptr(),
);
assert_eq!(status, RssnStatus::NullPointer);
}
#[test]
fn get_packed_size_query() {
let builder = rssn_dag_new();
let x = rssn_dag_variable(builder, c"x".as_ptr());
let c = rssn_dag_constant(builder, 3.0);
let _ = rssn_dag_add(builder, x, c);
let mut needed: usize = 0;
let status = rssn_dag_get_packed(builder as *const _, std::ptr::null_mut(), 0, &mut needed);
assert_eq!(status, RssnStatus::Success);
assert!(needed > 0, "packed snapshot must have positive size");
let mut buf = vec![0u8; needed];
let mut written: usize = 0;
let status2 =
rssn_dag_get_packed(builder as *const _, buf.as_mut_ptr(), needed, &mut written);
assert_eq!(status2, RssnStatus::Success);
assert_eq!(written, needed);
let node_count = u64::from_le_bytes(buf[0..8].try_into().expect("8 bytes"));
assert!(node_count >= 3, "at least 3 nodes: x, 3.0, x+3.0");
rssn_dag_free(builder);
}
}
use crate::custom::descriptor::{CustomOpDescriptor, CustomOpRegistry, EvalFn};
use std::sync::Arc;
pub struct RssnCustomOpRegistry(Arc<CustomOpRegistry>);
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_registry_new() -> *mut RssnCustomOpRegistry {
let result = catch_unwind(|| {
Box::into_raw(Box::new(RssnCustomOpRegistry(Arc::new(
CustomOpRegistry::new(),
))))
});
result.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_registry_free(reg: *mut RssnCustomOpRegistry) {
if reg.is_null() {
return;
}
let _ = catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
drop(Box::from_raw(reg));
}));
}
fn registry_mut(reg: *mut RssnCustomOpRegistry) -> Option<&'static mut CustomOpRegistry> {
if reg.is_null() {
return None;
}
let wrapper = unsafe { &mut *reg };
Arc::get_mut(&mut wrapper.0)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_register_fn1(
reg: *mut RssnCustomOpRegistry,
fn_id: u32,
name: *const c_char,
eval_fn: Option<extern "C" fn(f64) -> f64>,
vectorizable: u8,
) -> RssnStatus {
if reg.is_null() || name.is_null() {
return RssnStatus::NullPointer;
}
let Some(eval_fn) = eval_fn else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let name_str = unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|_| RssnStatus::ParseError)?
.to_owned();
let reg_mut = registry_mut(reg).ok_or(RssnStatus::InvalidNode)?;
let desc = CustomOpDescriptor::builder(
crate::dag::symbol::FnId(fn_id),
name_str,
EvalFn::Arity1(eval_fn),
)
.cost(2.0);
let desc = if vectorizable != 0 {
desc.vectorizable()
} else {
desc
};
reg_mut
.register(desc.build())
.map_err(|_| RssnStatus::RuleConflict)?;
Ok(RssnStatus::Success)
}));
result
.unwrap_or(Err(RssnStatus::Panic))
.unwrap_or_else(|e| e)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_register_fn2(
reg: *mut RssnCustomOpRegistry,
fn_id: u32,
name: *const c_char,
eval_fn: Option<extern "C" fn(f64, f64) -> f64>,
vectorizable: u8,
) -> RssnStatus {
if reg.is_null() || name.is_null() {
return RssnStatus::NullPointer;
}
let Some(eval_fn) = eval_fn else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let name_str = unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|_| RssnStatus::ParseError)?
.to_owned();
let reg_mut = registry_mut(reg).ok_or(RssnStatus::InvalidNode)?;
let desc = CustomOpDescriptor::builder(
crate::dag::symbol::FnId(fn_id),
name_str,
EvalFn::Arity2(eval_fn),
)
.cost(2.0);
let desc = if vectorizable != 0 {
desc.vectorizable()
} else {
desc
};
reg_mut
.register(desc.build())
.map_err(|_| RssnStatus::RuleConflict)?;
Ok(RssnStatus::Success)
}));
result
.unwrap_or(Err(RssnStatus::Panic))
.unwrap_or_else(|e| e)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_register_fn3(
reg: *mut RssnCustomOpRegistry,
fn_id: u32,
name: *const c_char,
eval_fn: Option<extern "C" fn(f64, f64, f64) -> f64>,
vectorizable: u8,
) -> RssnStatus {
if reg.is_null() || name.is_null() {
return RssnStatus::NullPointer;
}
let Some(eval_fn) = eval_fn else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let name_str = unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|_| RssnStatus::ParseError)?
.to_owned();
let reg_mut = registry_mut(reg).ok_or(RssnStatus::InvalidNode)?;
let desc = CustomOpDescriptor::builder(
crate::dag::symbol::FnId(fn_id),
name_str,
EvalFn::Arity3(eval_fn),
)
.cost(2.0);
let desc = if vectorizable != 0 {
desc.vectorizable()
} else {
desc
};
reg_mut
.register(desc.build())
.map_err(|_| RssnStatus::RuleConflict)?;
Ok(RssnStatus::Success)
}));
result
.unwrap_or(Err(RssnStatus::Panic))
.unwrap_or_else(|e| e)
}
const fn symbol_kind_to_u8(kind: &crate::dag::symbol::SymbolKind) -> u8 {
use crate::dag::symbol::{OpKind, SymbolKind};
match kind {
SymbolKind::Variable(_) => 0,
SymbolKind::Constant(_) => 1,
SymbolKind::Operator(OpKind::Add) => 2,
SymbolKind::Operator(OpKind::Sub) => 3,
SymbolKind::Operator(OpKind::Mul) => 4,
SymbolKind::Operator(OpKind::Div) => 5,
SymbolKind::Operator(OpKind::Pow) => 6,
SymbolKind::Operator(OpKind::Neg) => 7,
SymbolKind::Operator(OpKind::Mod) => 8,
SymbolKind::Function(_) => 9,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_add_simplify_rule(
reg: *mut RssnCustomOpRegistry,
fn_id: u32,
rule_name: *const c_char,
priority: i32,
callback: Option<RssnRuleCallback>,
user_data: *mut c_void,
) -> RssnStatus {
if reg.is_null() || rule_name.is_null() {
return RssnStatus::NullPointer;
}
let Some(callback) = callback else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let name_str = unsafe { CStr::from_ptr(rule_name) }
.to_str()
.map_err(|_| RssnStatus::ParseError)?
.to_owned();
let reg_mut = registry_mut(reg).ok_or(RssnStatus::InvalidNode)?;
let target_id = crate::dag::symbol::FnId(fn_id);
let desc = reg_mut
.get_mut(target_id)
.ok_or(RssnStatus::InvalidNodeId)?;
let ud = user_data as usize;
desc.simplify_rules
.push(crate::custom::descriptor::SimplifyRule {
name: name_str,
priority,
rule: std::sync::Arc::new(
move |builder: &mut DagBuilder,
kind: crate::dag::symbol::SymbolKind,
children: &[DagNodeId]| {
let kind_byte = symbol_kind_to_u8(&kind);
let child_ids: Vec<u32> = children.iter().map(|id| id.value()).collect();
let result = unsafe {
callback(
std::ptr::from_mut::<DagBuilder>(builder),
kind_byte,
child_ids.as_ptr(),
child_ids.len() as u32,
ud as *mut c_void,
)
};
if result == u32::MAX {
None
} else {
Some(DagNodeId::new(result))
}
},
),
});
Ok(RssnStatus::Success)
}));
result
.unwrap_or(Err(RssnStatus::Panic))
.unwrap_or_else(|e| e)
}
#[unsafe(no_mangle)]
pub extern "C" fn rssn_custom_op_add_egraph_rule(
reg: *mut RssnCustomOpRegistry,
fn_id: u32,
after_builtins: u8,
callback: Option<RssnEGraphRuleCallback>,
user_data: *mut c_void,
) -> RssnStatus {
if reg.is_null() {
return RssnStatus::NullPointer;
}
let Some(callback) = callback else {
return RssnStatus::NullPointer;
};
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let reg_mut = registry_mut(reg).ok_or(RssnStatus::InvalidNode)?;
let target_id = crate::dag::symbol::FnId(fn_id);
let desc = reg_mut
.get_mut(target_id)
.ok_or(RssnStatus::InvalidNodeId)?;
let ud = user_data as usize;
desc.egraph_rules
.push(crate::custom::descriptor::EGraphRule {
after_builtins: after_builtins != 0,
rule: std::sync::Arc::new(
move |builder: &mut DagBuilder,
kind: &crate::dag::symbol::SymbolKind,
children: &[DagNodeId]| {
let kind_byte = symbol_kind_to_u8(kind);
let child_ids: Vec<u32> = children.iter().map(|id| id.value()).collect();
let result = unsafe {
callback(
std::ptr::from_mut::<DagBuilder>(builder),
kind_byte,
child_ids.as_ptr(),
child_ids.len() as u32,
ud as *mut c_void,
)
};
if result == u32::MAX {
None
} else {
Some(DagNodeId::new(result))
}
},
),
});
Ok(RssnStatus::Success)
}));
result
.unwrap_or(Err(RssnStatus::Panic))
.unwrap_or_else(|e| e)
}
#[cfg(feature = "cranelift-jit")]
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_compile_with_custom_ops(
builder: *mut DagBuilder,
root: u32,
reg: *mut RssnCustomOpRegistry,
out_fn: *mut *mut c_void,
) -> RssnStatus {
if builder.is_null() || out_fn.is_null() || reg.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let builder_ref = unsafe { &mut *builder };
let reg_ref = unsafe { &*reg };
reg_ref.0.register_with_builder(builder_ref);
let root_id = DagNodeId::new(root);
let ast = crate::ast::convert::dag_to_ast(builder_ref.arena(), root_id);
let ctx_mutex = crate::ffi::jit_context::global_jit_ctx();
let mut ctx = ctx_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ctx.compiler_mut()
.set_custom_op_registry(Arc::clone(®_ref.0));
ctx.compiler_mut()
.compile(&ast)
.map_or(RssnStatus::CompilationError, |f| {
unsafe { *out_fn = f as *mut c_void };
RssnStatus::Success
})
}));
result.unwrap_or(RssnStatus::Panic)
}
#[cfg(not(feature = "cranelift-jit"))]
#[unsafe(no_mangle)]
pub extern "C" fn rssn_dag_compile_with_custom_ops(
_builder: *mut DagBuilder,
_root: u32,
_reg: *mut RssnCustomOpRegistry,
_out_fn: *mut *mut c_void,
) -> RssnStatus {
RssnStatus::CompilationError
}
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_simplify_with_custom_ops(
builder: *mut DagBuilder,
root: u32,
reg: *mut RssnCustomOpRegistry,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || reg.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let builder_ref = unsafe { &mut *builder };
let reg_ref = unsafe { &*reg };
let rule_registry = reg_ref.0.build_rule_registry();
let config = HeuristicConfig::default();
let mut engine = HeuristicEngine::new(config, SearchStrategy::Greedy)
.with_rule_registry(std::sync::Arc::new(rule_registry));
let root_id = DagNodeId::new(root);
let simplified = engine.simplify(builder_ref, root_id);
unsafe { *out_id = simplified.value() };
RssnStatus::Success
}));
result.unwrap_or(RssnStatus::Panic)
}
#[unsafe(no_mangle)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn rssn_dag_egraph_with_custom_ops(
builder: *mut DagBuilder,
root: u32,
config: RssnEGraphConfig,
reg: *mut RssnCustomOpRegistry,
out_id: *mut u32,
) -> RssnStatus {
if builder.is_null() || reg.is_null() || out_id.is_null() {
return RssnStatus::NullPointer;
}
if root == u32::MAX {
return RssnStatus::InvalidNodeId;
}
let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
let builder_ref = unsafe { &mut *builder };
let reg_ref = unsafe { &*reg };
let eg_config = crate::egraph::egraph::EGraphConfig {
max_rounds: if config.max_rounds == 0 {
8
} else {
config.max_rounds as usize
},
max_merges: if config.max_merges == 0 {
512
} else {
config.max_merges as usize
},
max_new_nodes: if config.max_new_nodes == 0 {
1024
} else {
config.max_new_nodes as usize
},
strict_ieee754_signed_zero: config.strict_ieee754_signed_zero != 0,
..Default::default()
};
let root_id = DagNodeId::new(root);
let mut egraph = crate::egraph::egraph::EGraph::new(builder_ref, eg_config);
reg_ref.0.apply_to_egraph(&mut egraph);
egraph.saturate(root_id);
let best = egraph.extract(root_id);
unsafe { *out_id = best.value() };
RssnStatus::Success
}));
result.unwrap_or(RssnStatus::Panic)
}