use crate::core::{self, QueryResult};
use crate::machine::{Machine, OutputSink};
use std::alloc::{Layout, alloc, dealloc};
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
fn raw_alloc(len: usize) -> *mut u8 {
if len == 0 {
return std::ptr::NonNull::<u8>::dangling().as_ptr();
}
unsafe { alloc(Layout::from_size_align_unchecked(len, 1)) }
}
static MACHINE: AtomicPtr<Machine> = AtomicPtr::new(std::ptr::null_mut());
static DEFAULT_STEP_LIMIT: AtomicU64 = AtomicU64::new(0);
static DEFAULT_DEPTH_LIMIT: AtomicU64 = AtomicU64::new(0);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn plg_rt_set_machine(m: *mut Machine) {
unsafe { (*m).output = OutputSink::Capture(String::new()) };
DEFAULT_STEP_LIMIT.store(unsafe { (*m).step_limit }, Ordering::Relaxed);
DEFAULT_DEPTH_LIMIT.store(
unsafe { (*m).metacall_depth_limit } as u64,
Ordering::Relaxed,
);
MACHINE.store(m, Ordering::Relaxed);
}
#[unsafe(no_mangle)]
pub extern "C" fn plg_rt_alloc(len: u32) -> *mut u8 {
raw_alloc(len as usize)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn plg_rt_free(ptr: *mut u8, len: u32) {
if len == 0 {
return;
}
unsafe { dealloc(ptr, Layout::from_size_align_unchecked(len as usize, 1)) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn plg_rt_run_query(
qptr: *const u8,
qlen: u32,
limit: u32,
step_limit: u64,
depth_limit: u32,
) -> u64 {
let m = unsafe { &mut *MACHINE.load(Ordering::Relaxed) };
m.reset_per_query();
m.solution_limit = if limit == 0 {
None
} else {
Some(limit as usize)
};
m.step_limit = if step_limit != 0 {
step_limit
} else {
DEFAULT_STEP_LIMIT.load(Ordering::Relaxed)
};
m.metacall_depth_limit = if depth_limit != 0 {
depth_limit as usize
} else {
DEFAULT_DEPTH_LIMIT.load(Ordering::Relaxed) as usize
};
let q = std::str::from_utf8(unsafe { std::slice::from_raw_parts(qptr, qlen as usize) })
.unwrap_or("");
let mut buf = Vec::new();
match core::run_query(m, q) {
QueryResult::ParseError(msg) | QueryResult::RuntimeError(msg) => {
let _ = core::write_error_json(&mut buf, &msg);
}
QueryResult::Solutions => {
let exhausted = core::exhausted(m);
let _ = core::write_solutions_json(&mut buf, m, exhausted, m.captured_output());
}
}
let out = raw_alloc(buf.len());
unsafe { std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()) };
((buf.len() as u64) << 32) | (out as u32 as u64)
}
#[cfg(test)]
mod tests {
use super::*;
use plg_shared::StringInterner;
fn install() -> *mut Machine {
let m = Box::into_raw(Machine::new(StringInterner::new(), Vec::new()));
unsafe { plg_rt_set_machine(m) };
m
}
fn run(q: &str, limit: u32, step: u64, depth: u32) {
let b = q.as_bytes();
let _ = unsafe { plg_rt_run_query(b.as_ptr(), b.len() as u32, limit, step, depth) };
}
#[test]
fn zero_limits_restore_module_default_not_previous_request() {
let m = install();
let (def_step, def_depth) = unsafe { ((*m).step_limit, (*m).metacall_depth_limit) };
run("x", 0, 5_000, 50);
unsafe {
assert_eq!((*m).step_limit, 5_000);
assert_eq!((*m).metacall_depth_limit, 50);
}
run("x", 0, 0, 0);
unsafe {
assert_eq!(
(*m).step_limit,
def_step,
"step_limit must revert to the module default"
);
assert_eq!(
(*m).metacall_depth_limit,
def_depth,
"metacall_depth_limit must revert to the module default"
);
}
}
}