use crate::expression::Expression;
use crate::{EvalContext, Real};
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec::Vec;
use bumpalo::Bump;
use core::ffi::{CStr, c_char, c_void};
use core::ptr;
pub use crate::expression::Expression as ExpressionExport;
const BATCH_MAGIC: usize = 0x7A9F4E82; const BATCH_FREED: usize = 0x9C2E8B7D;
struct BatchWithArena {
magic: usize, arena: *mut Bump, batch: *mut Expression<'static>, }
impl Drop for BatchWithArena {
fn drop(&mut self) {
self.magic = BATCH_FREED;
if !self.batch.is_null() {
unsafe {
drop(Box::from_raw(self.batch));
}
self.batch = ptr::null_mut();
}
if !self.arena.is_null() {
unsafe {
let mut arena_box = Box::from_raw(self.arena);
arena_box.reset();
drop(arena_box);
}
self.arena = ptr::null_mut();
}
}
}
#[cfg(feature = "alloc_tracking")]
static TOTAL_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "alloc_tracking")]
static TOTAL_FREED: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "alloc_tracking")]
static ALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "alloc_tracking")]
static FREE_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "alloc_tracking")]
mod allocation_tracking {
use core::cell::RefCell;
use critical_section::Mutex;
use heapless::{FnvIndexMap, Vec};
#[derive(Clone, Copy)]
pub struct AllocationInfo {
pub size: usize,
pub line: u32,
pub file: &'static str,
pub ptr: usize,
pub caller_addr: usize, pub caller2_addr: usize, }
#[cfg(target_arch = "arm")]
unsafe fn get_caller_addresses() -> (usize, usize) {
let lr: usize;
unsafe {
core::arch::asm!("mov {}, lr", out(reg) lr);
}
(lr, 0)
}
#[cfg(not(target_arch = "arm"))]
unsafe fn get_caller_addresses() -> (usize, usize) {
(0, 0) }
const MAX_TRACKED_ALLOCATIONS: usize = 512;
type TrackedAllocations = FnvIndexMap<usize, AllocationInfo, MAX_TRACKED_ALLOCATIONS>;
static TRACKED_ALLOCATIONS: Mutex<RefCell<TrackedAllocations>> =
Mutex::new(RefCell::new(TrackedAllocations::new()));
pub fn track_allocation(ptr: *mut u8, size: usize, location: &'static core::panic::Location) {
if ptr.is_null() {
return;
}
let (caller_addr, caller2_addr) = unsafe { get_caller_addresses() };
let info = AllocationInfo {
size,
line: location.line(),
file: location.file(),
ptr: ptr as usize,
caller_addr,
caller2_addr,
};
critical_section::with(|cs| {
let mut tracked = TRACKED_ALLOCATIONS.borrow(cs).borrow_mut();
let _ = tracked.insert(ptr as usize, info);
});
}
pub fn untrack_allocation(ptr: *mut u8) {
if ptr.is_null() {
return;
}
critical_section::with(|cs| {
let mut tracked = TRACKED_ALLOCATIONS.borrow(cs).borrow_mut();
tracked.remove(&(ptr as usize));
});
}
pub fn get_remaining_allocations() -> Vec<AllocationInfo, MAX_TRACKED_ALLOCATIONS> {
critical_section::with(|cs| {
let tracked = TRACKED_ALLOCATIONS.borrow(cs).borrow();
let mut result = Vec::new();
for (_, info) in tracked.iter() {
let _ = result.push(*info);
}
result
})
}
}
#[cfg(feature = "custom_cbindgen_alloc")]
mod embedded_allocator {
use super::*;
use core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::{AtomicUsize, Ordering};
use embedded_alloc::TlsfHeap;
use core::sync::atomic::AtomicBool;
pub struct TrackingHeap {
heap: TlsfHeap,
initialized: AtomicBool,
}
impl TrackingHeap {
pub const fn new() -> Self {
Self {
heap: TlsfHeap::empty(),
initialized: AtomicBool::new(false),
}
}
pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::Acquire)
}
pub unsafe fn init(&self, start_addr: usize, size: usize) {
unsafe {
self.heap.init(start_addr, size);
}
self.initialized.store(true, Ordering::Release);
}
fn ensure_initialized(&self) {
if !self.initialized.load(Ordering::Acquire) {
panic!("Heap not initialized! Call exp_rs_heap_init() before any allocations");
}
}
}
unsafe impl GlobalAlloc for TrackingHeap {
#[track_caller]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.ensure_initialized();
let ptr = unsafe { self.heap.alloc(layout) };
if !ptr.is_null() {
#[cfg(feature = "alloc_tracking")]
{
TOTAL_ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
ALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed);
let location = core::panic::Location::caller();
allocation_tracking::track_allocation(ptr, layout.size(), location);
}
}
ptr
}
#[track_caller]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.ensure_initialized();
unsafe {
self.heap.dealloc(ptr, layout);
}
#[cfg(feature = "alloc_tracking")]
{
TOTAL_FREED.fetch_add(layout.size(), Ordering::Relaxed);
FREE_COUNT.fetch_add(1, Ordering::Relaxed);
}
#[cfg(feature = "alloc_tracking")]
{
allocation_tracking::untrack_allocation(ptr);
}
}
}
#[global_allocator]
pub static HEAP: TrackingHeap = TrackingHeap::new();
pub static CURRENT_HEAP_SIZE: AtomicUsize = AtomicUsize::new(0);
}
#[cfg(not(feature = "custom_cbindgen_alloc"))]
mod system_allocator {
extern crate std;
use std::alloc::{GlobalAlloc, Layout, System};
pub struct TrackingSystemHeap;
unsafe impl GlobalAlloc for TrackingSystemHeap {
#[track_caller]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc(layout) };
#[cfg(feature = "alloc_tracking")]
if !ptr.is_null() {
TOTAL_ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
ALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed);
let location = core::panic::Location::caller();
allocation_tracking::track_allocation(ptr, layout.size(), location);
}
ptr
}
#[track_caller]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe {
System.dealloc(ptr, layout);
}
#[cfg(feature = "alloc_tracking")]
{
TOTAL_FREED.fetch_add(layout.size(), Ordering::Relaxed);
FREE_COUNT.fetch_add(1, Ordering::Relaxed);
allocation_tracking::untrack_allocation(ptr);
}
}
}
#[global_allocator]
pub static HEAP: TrackingSystemHeap = TrackingSystemHeap;
}
#[cfg(feature = "custom_cbindgen_alloc")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_heap_init(heap_ptr: *mut u8, heap_size: usize) -> i32 {
use embedded_allocator::*;
if heap_ptr.is_null() {
return -1; }
if heap_size == 0 {
return -3; }
if HEAP.is_initialized() {
return -2; }
unsafe {
HEAP.init(heap_ptr as usize, heap_size);
CURRENT_HEAP_SIZE.store(heap_size, core::sync::atomic::Ordering::Release);
}
0
}
#[cfg(feature = "custom_cbindgen_alloc")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_heap_size() -> usize {
embedded_allocator::CURRENT_HEAP_SIZE.load(core::sync::atomic::Ordering::Acquire)
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_total_allocated() -> usize {
TOTAL_ALLOCATED.load(Ordering::Relaxed)
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_total_freed() -> usize {
TOTAL_FREED.load(Ordering::Relaxed)
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_allocation_count() -> usize {
ALLOCATION_COUNT.load(Ordering::Relaxed)
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_free_count() -> usize {
FREE_COUNT.load(Ordering::Relaxed)
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_current_allocated() -> usize {
let allocated = TOTAL_ALLOCATED.load(Ordering::Relaxed);
let freed = TOTAL_FREED.load(Ordering::Relaxed);
allocated.saturating_sub(freed)
}
#[cfg(feature = "alloc_tracking")]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CAllocationInfo {
pub size: usize,
pub line: u32,
pub file_ptr: *const c_char,
pub ptr: usize,
pub caller_addr: usize, pub caller2_addr: usize, }
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_remaining_allocation_count() -> usize {
use allocation_tracking::*;
let remaining = get_remaining_allocations();
remaining.len()
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_remaining_allocation_by_index(allocation_index: usize) -> ExprResult {
use allocation_tracking::*;
let remaining = get_remaining_allocations();
if allocation_index >= remaining.len() {
return ExprResult::from_ffi_error(-1, "Allocation index out of bounds");
}
let allocation = &remaining[allocation_index];
let info_str = allocation.file;
ExprResult {
status: 0,
value: allocation.size as Real,
index: allocation.line as i32,
error: ExprResult::copy_to_error_buffer(info_str),
}
}
#[cfg(feature = "alloc_tracking")]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_get_remaining_allocations(
output_buffer: *mut CAllocationInfo,
buffer_size: usize,
) -> usize {
use allocation_tracking::*;
let remaining = get_remaining_allocations();
if output_buffer.is_null() {
return remaining.len();
}
let copy_count = core::cmp::min(remaining.len(), buffer_size);
for (i, allocation) in remaining.iter().enumerate().take(copy_count) {
unsafe {
let c_info = CAllocationInfo {
size: allocation.size,
line: allocation.line,
file_ptr: allocation.file.as_ptr() as *const c_char,
ptr: allocation.ptr,
caller_addr: allocation.caller_addr,
caller2_addr: allocation.caller2_addr,
};
output_buffer.add(i).write(c_info);
}
}
copy_count
}
#[cfg(all(target_arch = "arm", not(test)))]
#[unsafe(no_mangle)]
fn _critical_section_1_0_acquire() -> critical_section::RawRestoreState {
let primask: u32;
unsafe {
core::arch::asm!("mrs {}, primask", out(reg) primask);
core::arch::asm!("cpsid i");
}
primask
}
#[cfg(all(target_arch = "arm", not(test)))]
#[unsafe(no_mangle)]
unsafe fn _critical_section_1_0_release(restore_state: critical_section::RawRestoreState) {
if restore_state & 1 == 0 {
unsafe {
core::arch::asm!("cpsie i");
}
}
}
#[cfg(not(all(target_arch = "arm", not(test))))]
#[unsafe(no_mangle)]
fn _critical_section_1_0_acquire() -> critical_section::RawRestoreState {
0
}
#[cfg(not(all(target_arch = "arm", not(test))))]
#[unsafe(no_mangle)]
unsafe fn _critical_section_1_0_release(_restore_state: critical_section::RawRestoreState) {
}
#[allow(dead_code)]
static mut EXP_RS_PANIC_FLAG: *mut i32 = ptr::null_mut();
#[allow(dead_code)]
static mut EXP_RS_LOG_FUNCTION: *const c_void = ptr::null();
#[allow(dead_code)]
type LogFunctionType = unsafe extern "C" fn(*const u8, usize);
#[allow(dead_code)]
static PANIC_DEFAULT_MSG: &[u8] = b"Rust panic occurred\0";
#[cfg(not(test))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn exp_rs_register_panic_handler(
flag_ptr: *mut i32,
log_func: *const c_void,
) {
unsafe {
EXP_RS_PANIC_FLAG = flag_ptr;
EXP_RS_LOG_FUNCTION = log_func;
}
}
#[repr(C)]
pub struct ExprResult {
status: i32,
value: Real,
index: i32,
error: [c_char; crate::types::EXP_RS_ERROR_BUFFER_SIZE],
}
impl ExprResult {
fn copy_to_error_buffer(msg: &str) -> [c_char; crate::types::EXP_RS_ERROR_BUFFER_SIZE] {
let mut buffer = [0; crate::types::EXP_RS_ERROR_BUFFER_SIZE];
let bytes = msg.as_bytes();
let copy_len = core::cmp::min(bytes.len(), crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1);
for i in 0..copy_len {
buffer[i] = bytes[i] as c_char;
}
buffer[copy_len] = 0; buffer
}
fn success_value(value: Real) -> Self {
ExprResult {
status: 0,
value,
index: 0,
error: [0; crate::types::EXP_RS_ERROR_BUFFER_SIZE],
}
}
fn success_index(index: usize) -> Self {
ExprResult {
status: 0,
value: 0.0,
index: index as i32,
error: [0; crate::types::EXP_RS_ERROR_BUFFER_SIZE],
}
}
fn from_expr_error(err: crate::error::ExprError) -> Self {
let error_code = err.error_code();
let error_msg = err.to_string();
ExprResult {
status: error_code,
value: Real::NAN,
index: -1,
error: Self::copy_to_error_buffer(&error_msg),
}
}
fn from_ffi_error(code: i32, msg: &str) -> Self {
ExprResult {
status: code,
value: Real::NAN,
index: -1,
error: Self::copy_to_error_buffer(msg),
}
}
}
pub const FFI_ERROR_NULL_POINTER: i32 = -1;
pub const FFI_ERROR_INVALID_UTF8: i32 = -2;
pub const FFI_ERROR_NO_ARENA_AVAILABLE: i32 = -3;
pub const FFI_ERROR_CANNOT_GET_MUTABLE_ACCESS: i32 = -4;
pub const FFI_ERROR_INVALID_POINTER: i32 = -5;
#[repr(C)]
pub struct ExprContext {
_private: [u8; 0],
}
#[repr(C)]
pub struct ExprBatch {
_private: [u8; 0],
}
#[repr(C)]
pub struct ExprArena {
_private: [u8; 0],
}
pub type NativeFunc = extern "C" fn(args: *const Real, n_args: usize) -> Real;
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_new() -> *mut ExprContext {
let ctx = EvalContext::new();
let ctx_rc = alloc::rc::Rc::new(ctx);
let ctx = Box::new(ctx_rc);
Box::into_raw(ctx) as *mut ExprContext
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_new_empty() -> *mut ExprContext {
let ctx = EvalContext::empty();
let ctx_rc = alloc::rc::Rc::new(ctx);
let ctx = Box::new(ctx_rc);
Box::into_raw(ctx) as *mut ExprContext
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_free(ctx: *mut ExprContext) {
if ctx.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(ctx as *mut alloc::rc::Rc<EvalContext>);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_native_function_count(ctx: *const ExprContext) -> usize {
if ctx.is_null() {
return 0;
}
unsafe {
let ctx = &*(ctx as *const alloc::rc::Rc<EvalContext>);
ctx.list_native_functions().len()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_get_native_function_name(
ctx: *const ExprContext,
index: usize,
buffer: *mut u8,
buffer_size: usize,
) -> usize {
if ctx.is_null() {
return 0;
}
unsafe {
let ctx = &*(ctx as *const alloc::rc::Rc<EvalContext>);
let functions = ctx.list_native_functions();
if index >= functions.len() {
return 0;
}
let name = &functions[index];
let name_bytes = name.as_bytes();
if buffer.is_null() {
return name_bytes.len();
}
let copy_len = core::cmp::min(name_bytes.len(), buffer_size);
core::ptr::copy_nonoverlapping(name_bytes.as_ptr(), buffer, copy_len);
name_bytes.len()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_context_add_function(
ctx: *mut ExprContext,
name: *const c_char,
arity: usize,
func: NativeFunc,
) -> i32 {
if ctx.is_null() || name.is_null() {
return -1;
}
let ctx_handle = unsafe { &mut *(ctx as *mut alloc::rc::Rc<EvalContext>) };
let name_cstr = unsafe { CStr::from_ptr(name) };
let name_str = match name_cstr.to_str() {
Ok(s) => s,
Err(_) => return -2, };
let implementation = move |args: &[Real]| -> Real {
if args.len() != arity {
return Real::NAN;
}
func(args.as_ptr(), args.len())
};
match alloc::rc::Rc::get_mut(ctx_handle) {
Some(ctx_mut) => {
match ctx_mut.register_native_function(name_str, arity, implementation) {
Ok(_) => 0,
Err(_) => -3, }
}
None => -4, }
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_add_expression_function(
batch: *mut ExprBatch,
name: *const c_char,
params: *const c_char,
expression: *const c_char,
) -> i32 {
if batch.is_null() || name.is_null() || params.is_null() || expression.is_null() {
return -1;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let name_cstr = unsafe { CStr::from_ptr(name) };
let name_str = match name_cstr.to_str() {
Ok(s) => s,
Err(_) => return -2, };
let params_cstr = unsafe { CStr::from_ptr(params) };
let params_str = match params_cstr.to_str() {
Ok(s) => s,
Err(_) => return -2, };
let expr_cstr = unsafe { CStr::from_ptr(expression) };
let expr_str = match expr_cstr.to_str() {
Ok(s) => s,
Err(_) => return -2, };
let param_vec: Vec<&str> = if params_str.is_empty() {
Vec::new()
} else {
params_str.split(',').map(|s| s.trim()).collect()
};
match builder.register_expression_function(name_str, ¶m_vec, expr_str) {
Ok(_) => 0,
Err(_) => -3, }
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_remove_expression_function(
batch: *mut ExprBatch,
name: *const c_char,
) -> i32 {
if batch.is_null() || name.is_null() {
return -1;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let name_cstr = unsafe { CStr::from_ptr(name) };
let name_str = match name_cstr.to_str() {
Ok(s) => s,
Err(_) => return -2, };
match builder.unregister_expression_function(name_str) {
Ok(was_removed) => {
if was_removed {
1
} else {
0
}
}
Err(_) => -3, }
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_new(size_hint: usize) -> *mut ExprBatch {
let arena_size = if size_hint == 0 { 8192 } else { size_hint };
let arena = Box::new(Bump::with_capacity(arena_size));
let arena_ptr = Box::into_raw(arena);
let arena_ref: &'static Bump = unsafe { &*arena_ptr };
let batch = Box::new(Expression::new(arena_ref));
let batch_ptr = Box::into_raw(batch);
let wrapper = Box::new(BatchWithArena {
magic: BATCH_MAGIC,
arena: arena_ptr,
batch: batch_ptr,
});
Box::into_raw(wrapper) as *mut ExprBatch
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_is_valid(batch: *const ExprBatch) -> ExprResult {
if batch.is_null() {
return ExprResult::from_ffi_error(FFI_ERROR_NULL_POINTER, "Batch pointer is NULL");
}
unsafe {
let wrapper = batch as *const BatchWithArena;
let magic = (*wrapper).magic;
if magic == BATCH_MAGIC {
ExprResult::success_value(1.0)
} else if magic == BATCH_FREED {
ExprResult::from_ffi_error(
FFI_ERROR_INVALID_POINTER,
"Batch has already been freed (double-free detected)",
)
} else {
ExprResult::from_ffi_error(
FFI_ERROR_INVALID_POINTER,
"Invalid or corrupted batch pointer",
)
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_free(batch: *mut ExprBatch) {
if batch.is_null() {
return;
}
unsafe {
let wrapper = batch as *mut BatchWithArena;
let magic = (*wrapper).magic;
if magic == BATCH_FREED {
#[cfg(debug_assertions)]
panic!("Double-free detected on ExprBatch at {:p}", batch);
#[cfg(not(debug_assertions))]
return; }
if magic != BATCH_MAGIC {
#[cfg(debug_assertions)]
panic!(
"Invalid ExprBatch pointer at {:p} (magic: 0x{:x})",
batch, magic
);
#[cfg(not(debug_assertions))]
return; }
let _ = Box::from_raw(wrapper);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_clear(batch: *mut ExprBatch) -> i32 {
if batch.is_null() {
return FFI_ERROR_NULL_POINTER;
}
unsafe {
let wrapper = &mut *(batch as *mut BatchWithArena);
if wrapper.magic != BATCH_MAGIC {
#[cfg(debug_assertions)]
panic!(
"Invalid or freed ExprBatch pointer at {:p} (magic: 0x{:x})",
batch, wrapper.magic
);
#[cfg(not(debug_assertions))]
return FFI_ERROR_INVALID_POINTER; }
(*wrapper.batch).clear();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_add_expression(
batch: *mut ExprBatch,
expr: *const c_char,
) -> ExprResult {
if batch.is_null() || expr.is_null() {
return ExprResult::from_ffi_error(
FFI_ERROR_NULL_POINTER,
"Null pointer passed to expr_batch_add_expression",
);
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let expr_cstr = unsafe { CStr::from_ptr(expr) };
let expr_str = match expr_cstr.to_str() {
Ok(s) => s,
Err(_) => {
return ExprResult::from_ffi_error(
FFI_ERROR_INVALID_UTF8,
"Invalid UTF-8 in expression string",
);
}
};
match builder.add_expression(expr_str) {
Ok(idx) => ExprResult::success_index(idx),
Err(e) => ExprResult::from_expr_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_add_variable(
batch: *mut ExprBatch,
name: *const c_char,
value: Real,
) -> ExprResult {
if batch.is_null() || name.is_null() {
return ExprResult::from_ffi_error(
FFI_ERROR_NULL_POINTER,
"Null pointer passed to expr_batch_add_variable",
);
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let name_cstr = unsafe { CStr::from_ptr(name) };
let name_str = match name_cstr.to_str() {
Ok(s) => s,
Err(_) => {
return ExprResult::from_ffi_error(
FFI_ERROR_INVALID_UTF8,
"Invalid UTF-8 in variable name",
);
}
};
match builder.add_parameter(name_str, value) {
Ok(idx) => ExprResult::success_index(idx),
Err(e) => ExprResult::from_expr_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_set_variable(batch: *mut ExprBatch, index: usize, value: Real) -> i32 {
if batch.is_null() {
return -1;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
match builder.set_param(index, value) {
Ok(_) => 0,
Err(_) => -2, }
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_evaluate(batch: *mut ExprBatch, ctx: *mut ExprContext) -> i32 {
if batch.is_null() {
return -1;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let eval_ctx = if ctx.is_null() {
alloc::rc::Rc::new(EvalContext::new())
} else {
unsafe {
let ctx_rc = &*(ctx as *const alloc::rc::Rc<EvalContext>);
ctx_rc.clone()
}
};
match builder.eval(&eval_ctx) {
Ok(_) => 0,
Err(_) => -2, }
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_get_result(batch: *const ExprBatch, index: usize) -> Real {
if batch.is_null() {
return Real::NAN;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &*wrapper.batch };
builder.get_result(index).unwrap_or(Real::NAN)
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_arena_bytes(batch: *const ExprBatch) -> usize {
if batch.is_null() {
return 0;
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &*wrapper.batch };
builder.arena_allocated_bytes()
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_batch_evaluate_ex(
batch: *mut ExprBatch,
ctx: *mut ExprContext,
) -> ExprResult {
if batch.is_null() {
return ExprResult::from_ffi_error(FFI_ERROR_NULL_POINTER, "Null batch pointer");
}
let wrapper = unsafe { &*(batch as *const BatchWithArena) };
let builder = unsafe { &mut *wrapper.batch };
let eval_ctx = if ctx.is_null() {
alloc::rc::Rc::new(EvalContext::new())
} else {
unsafe {
let ctx_rc = &*(ctx as *const alloc::rc::Rc<EvalContext>);
ctx_rc.clone()
}
};
match builder.eval(&eval_ctx) {
Ok(_) => ExprResult::success_value(0.0), Err(e) => ExprResult::from_expr_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn expr_estimate_arena_size(
expression_count: usize,
total_expr_length: usize,
param_count: usize,
_estimated_iterations: usize,
) -> usize {
let expr_overhead = expression_count * 512;
let string_storage = total_expr_length * 2;
let param_storage = param_count * 64;
let total = expr_overhead + string_storage + param_storage;
total + (total / 2)
}
#[cfg(debug_assertions)]
#[unsafe(no_mangle)]
pub extern "C" fn exp_rs_test_trigger_panic() {
panic!("Test panic triggered from C");
}
#[cfg(all(not(test), target_arch = "arm"))]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
unsafe {
if !EXP_RS_PANIC_FLAG.is_null() {
*EXP_RS_PANIC_FLAG = 1;
}
if !EXP_RS_LOG_FUNCTION.is_null() {
let log_func: LogFunctionType = core::mem::transmute(EXP_RS_LOG_FUNCTION);
if let Some(location) = info.location() {
let file = location.file();
let _line = location.line();
log_func(file.as_ptr(), file.len());
} else {
log_func(PANIC_DEFAULT_MSG.as_ptr(), PANIC_DEFAULT_MSG.len() - 1);
}
}
}
#[cfg(target_arch = "arm")]
loop {
unsafe {
core::arch::asm!("udf #0");
}
}
#[cfg(not(target_arch = "arm"))]
loop {
core::hint::spin_loop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_buffer_null_termination() {
use core::ffi::c_char;
let short_msg = "Test error message";
let buffer = ExprResult::copy_to_error_buffer(short_msg);
let mut found_null = false;
for (i, &byte) in buffer.iter().enumerate() {
if byte == 0 {
found_null = true;
let recovered_msg = unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
buffer.as_ptr() as *const u8,
i,
))
};
assert_eq!(recovered_msg, short_msg);
break;
}
}
assert!(found_null, "Error buffer should be null terminated");
let max_msg = "a".repeat(crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1);
let buffer = ExprResult::copy_to_error_buffer(&max_msg);
assert_eq!(buffer[crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1], 0);
assert_eq!(
buffer[crate::types::EXP_RS_ERROR_BUFFER_SIZE - 2],
b'a' as c_char
);
let long_msg = "a".repeat(crate::types::EXP_RS_ERROR_BUFFER_SIZE + 10);
let buffer = ExprResult::copy_to_error_buffer(&long_msg);
assert_eq!(buffer[crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1], 0);
let recovered_msg = unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
buffer.as_ptr() as *const u8,
crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1,
))
};
assert_eq!(
recovered_msg.len(),
crate::types::EXP_RS_ERROR_BUFFER_SIZE - 1
);
assert!(recovered_msg.chars().all(|c| c == 'a'));
}
}