use crate::stack::{Stack, pop, push};
use crate::value::Value;
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
type ClosureEntry = (usize, Box<[Value]>);
static SPAWN_CLOSURE_REGISTRY: LazyLock<Mutex<HashMap<i64, ClosureEntry>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
struct SpawnRegistryGuard {
closure_spawn_id: i64,
should_cleanup: bool,
}
impl SpawnRegistryGuard {
fn new(closure_spawn_id: i64) -> Self {
Self {
closure_spawn_id,
should_cleanup: true,
}
}
fn disarm(&mut self) {
self.should_cleanup = false;
}
}
impl Drop for SpawnRegistryGuard {
fn drop(&mut self) {
if self.should_cleanup {
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
if let Some((_, env)) = registry.remove(&self.closure_spawn_id) {
drop(env);
}
}
}
}
extern "C" fn closure_spawn_trampoline(stack: Stack) -> Stack {
unsafe {
let (stack, closure_spawn_id_val) = pop(stack);
let closure_spawn_id = match closure_spawn_id_val {
Value::Int(id) => id,
_ => panic!(
"closure_spawn_trampoline: expected Int (closure_spawn_id), got {:?}",
closure_spawn_id_val
),
};
let (fn_ptr, env) = {
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
registry.remove(&closure_spawn_id).unwrap_or_else(|| {
panic!(
"closure_spawn_trampoline: no data for closure_spawn_id {}",
closure_spawn_id
)
})
};
let env_ptr = env.as_ptr();
let env_len = env.len();
let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
std::mem::transmute(fn_ptr);
fn_ref(stack, env_ptr, env_len)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_push_quotation(
stack: Stack,
wrapper: usize,
impl_: usize,
) -> Stack {
debug_assert!(
wrapper != 0,
"push_quotation: wrapper function pointer is null"
);
debug_assert!(impl_ != 0, "push_quotation: impl function pointer is null");
unsafe { push(stack, Value::Quotation { wrapper, impl_ }) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_peek_is_quotation(stack: Stack) -> i64 {
use crate::stack::peek;
unsafe {
let value = peek(stack);
match value {
Value::Quotation { .. } => 1,
_ => 0,
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_peek_quotation_fn_ptr(stack: Stack) -> usize {
use crate::stack::peek;
unsafe {
let value = peek(stack);
match value {
Value::Quotation { impl_, .. } => {
debug_assert!(
impl_ != 0,
"peek_quotation_fn_ptr: impl function pointer is null"
);
impl_
}
_ => {
debug_assert!(
false,
"peek_quotation_fn_ptr: expected Quotation, got {:?}",
value
);
0
}
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_call(stack: Stack) -> Stack {
unsafe {
let (stack, value) = pop(stack);
match value {
Value::Quotation { wrapper, .. } => {
if wrapper == 0 {
panic!("call: quotation wrapper function pointer is null");
}
let fn_ref: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(wrapper);
fn_ref(stack)
}
Value::Closure { fn_ptr, env } => {
if fn_ptr == 0 {
panic!("call: closure function pointer is null");
}
let env_data = env.as_ptr();
let env_len = env.len();
let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
std::mem::transmute(fn_ptr);
fn_ref(stack, env_data, env_len)
}
_ => panic!(
"call: expected Quotation or Closure on stack, got {:?}",
value
),
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_spawn(stack: Stack) -> Stack {
use crate::scheduler::patch_seq_strand_spawn_with_base;
use crate::stack::clone_stack_with_base;
unsafe {
let (stack, value) = pop(stack);
match value {
Value::Quotation { wrapper, .. } => {
if wrapper == 0 {
panic!("spawn: quotation wrapper function pointer is null");
}
let fn_ref: extern "C" fn(Stack) -> Stack = std::mem::transmute(wrapper);
let (child_stack, child_base) = clone_stack_with_base(stack);
let strand_id = patch_seq_strand_spawn_with_base(fn_ref, child_stack, child_base);
push(stack, Value::Int(strand_id))
}
Value::Closure { fn_ptr, env } => {
if fn_ptr == 0 {
panic!("spawn: closure function pointer is null");
}
use std::sync::atomic::{AtomicI64, Ordering};
static NEXT_CLOSURE_SPAWN_ID: AtomicI64 = AtomicI64::new(1);
let closure_spawn_id = NEXT_CLOSURE_SPAWN_ID.fetch_add(1, Ordering::Relaxed);
{
let env_box: Box<[Value]> = env.iter().cloned().collect();
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
registry.insert(closure_spawn_id, (fn_ptr, env_box));
}
let mut guard = SpawnRegistryGuard::new(closure_spawn_id);
let stack_base = crate::stack::alloc_stack();
let initial_stack = push(stack_base, Value::Int(closure_spawn_id));
let strand_id = patch_seq_strand_spawn_with_base(
closure_spawn_trampoline,
initial_stack,
stack_base,
);
guard.disarm();
push(stack, Value::Int(strand_id))
}
_ => panic!("spawn: expected Quotation or Closure, got {:?}", value),
}
}
}
#[inline]
pub unsafe fn invoke_callable(stack: Stack, callable: &Value) -> Stack {
unsafe {
match callable {
Value::Quotation { wrapper, .. } => {
let fn_ref: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(*wrapper);
fn_ref(stack)
}
Value::Closure { fn_ptr, env } => {
let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
std::mem::transmute(*fn_ptr);
fn_ref(stack, env.as_ptr(), env.len())
}
_ => panic!(
"invoke_callable: expected Quotation or Closure, got {:?}",
callable
),
}
}
}
pub use patch_seq_call as call;
pub use patch_seq_push_quotation as push_quotation;
pub use patch_seq_spawn as spawn;
#[cfg(test)]
mod tests {
use super::*;
use crate::arithmetic::push_int;
use crate::value::Value;
#[test]
fn test_spawn_registry_guard_cleanup() {
let closure_id = 12345;
let env: Box<[Value]> = vec![Value::Int(42), Value::Int(99)].into_boxed_slice();
let fn_ptr: usize = 0x1234;
{
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
registry.insert(closure_id, (fn_ptr, env));
}
{
let registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
assert!(registry.contains_key(&closure_id));
}
{
let _guard = SpawnRegistryGuard::new(closure_id);
}
{
let registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
assert!(
!registry.contains_key(&closure_id),
"Guard should have cleaned up registry entry on drop"
);
}
}
#[test]
fn test_spawn_registry_guard_disarm() {
let closure_id = 54321;
let env: Box<[Value]> = vec![Value::Int(10), Value::Int(20)].into_boxed_slice();
let fn_ptr: usize = 0x5678;
{
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
registry.insert(closure_id, (fn_ptr, env));
}
{
let mut guard = SpawnRegistryGuard::new(closure_id);
guard.disarm();
}
{
let registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
assert!(
registry.contains_key(&closure_id),
"Disarmed guard should not clean up registry entry"
);
drop(registry);
let mut registry = SPAWN_CLOSURE_REGISTRY.lock().unwrap();
registry.remove(&closure_id);
}
}
unsafe extern "C" fn add_one_quot(stack: Stack) -> Stack {
unsafe {
let stack = push_int(stack, 1);
crate::arithmetic::add(stack)
}
}
#[test]
fn test_push_quotation() {
unsafe {
let stack: Stack = crate::stack::alloc_test_stack();
let fn_ptr = add_one_quot as *const () as usize;
let stack = push_quotation(stack, fn_ptr, fn_ptr);
let (_stack, value) = pop(stack);
assert!(matches!(value, Value::Quotation { .. }));
}
}
#[test]
fn test_call_quotation() {
unsafe {
let stack: Stack = crate::stack::alloc_test_stack();
let stack = push_int(stack, 5);
let fn_ptr = add_one_quot as *const () as usize;
let stack = push_quotation(stack, fn_ptr, fn_ptr);
let stack = call(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Int(6));
}
}
unsafe extern "C" fn noop_quot(stack: Stack) -> Stack {
stack
}
#[test]
fn test_spawn_quotation() {
unsafe {
crate::scheduler::scheduler_init();
let stack: Stack = crate::stack::alloc_test_stack();
let fn_ptr = noop_quot as *const () as usize;
let stack = push_quotation(stack, fn_ptr, fn_ptr);
let stack = spawn(stack);
let (_stack, result) = pop(stack);
match result {
Value::Int(strand_id) => {
assert!(strand_id > 0, "Strand ID should be positive");
}
_ => panic!("Expected Int (strand ID), got {:?}", result),
}
crate::scheduler::wait_all_strands();
}
}
}