use std::ffi::{c_int, c_void};
use std::sync::Mutex;
use super::abi::{RawStub, TclStubs};
use super::generated::TCL_NAMES;
use super::trace::{record, Table};
macro_rules! entered {
($name:literal) => {
record(
Table::Tcl,
TCL_NAMES
.iter()
.position(|n| *n == $name)
.expect("no such slot"),
)
};
}
const TCL_DYNAMIC: usize = 3;
#[derive(Clone, Copy)]
struct Reference {
client_data: usize,
ref_count: usize,
must_free: bool,
free_proc: usize,
}
static REFS: Mutex<Vec<Reference>> = Mutex::new(Vec::new());
pub fn preserved() -> usize {
REFS.lock().expect("preserve table poisoned").len()
}
unsafe fn dispose(client_data: usize, free_proc: usize) {
if free_proc == TCL_DYNAMIC {
libc::free(client_data as *mut c_void);
} else {
let f: unsafe extern "C" fn(*mut c_void) = std::mem::transmute(free_proc);
f(client_data as *mut c_void);
}
}
pub unsafe extern "C" fn preserve(client_data: *mut c_void) {
entered!("tcl_Preserve");
let key = client_data as usize;
let mut refs = REFS.lock().expect("preserve table poisoned");
if let Some(r) = refs.iter_mut().find(|r| r.client_data == key) {
r.ref_count += 1;
return;
}
refs.push(Reference {
client_data: key,
ref_count: 1,
must_free: false,
free_proc: 0,
});
}
pub unsafe extern "C" fn release(client_data: *mut c_void) {
entered!("tcl_Release");
let key = client_data as usize;
let disposal = {
let mut refs = REFS.lock().expect("preserve table poisoned");
let Some(i) = refs.iter().position(|r| r.client_data == key) else {
panic!("Tcl_Release couldn't find reference for {client_data:?}");
};
if refs[i].ref_count > 1 {
refs[i].ref_count -= 1;
return;
}
let r = refs.swap_remove(i);
r.must_free.then_some((r.client_data, r.free_proc))
};
if let Some((data, proc_)) = disposal {
dispose(data, proc_);
}
}
pub unsafe extern "C" fn eventually_free(client_data: *mut c_void, free_proc: *mut c_void) {
entered!("tcl_EventuallyFree");
let key = client_data as usize;
{
let mut refs = REFS.lock().expect("preserve table poisoned");
if let Some(r) = refs.iter_mut().find(|r| r.client_data == key) {
assert!(
!r.must_free,
"Tcl_EventuallyFree called twice for {client_data:?}"
);
r.must_free = true;
r.free_proc = free_proc as usize;
return;
}
}
dispose(key, free_proc as usize);
}
pub unsafe extern "C" fn set_errno(code: c_int) {
entered!("tcl_SetErrno");
*libc::__error() = code;
}
pub unsafe extern "C" fn get_errno() -> c_int {
entered!("tcl_GetErrno");
*libc::__error()
}
pub unsafe fn install_impls(t: &mut TclStubs) -> Vec<usize> {
vec![
install(t, "tcl_Preserve", preserve as *const ()),
install(t, "tcl_Release", release as *const ()),
install(t, "tcl_EventuallyFree", eventually_free as *const ()),
install(t, "tcl_SetErrno", set_errno as *const ()),
install(t, "tcl_GetErrno", get_errno as *const ()),
]
}
unsafe fn install(t: &mut TclStubs, name: &str, f: *const ()) -> usize {
let i = TCL_NAMES
.iter()
.position(|n| *n == name)
.unwrap_or_else(|| panic!("no slot named {name} in TclStubs"));
t.slots[i] = std::mem::transmute::<*const (), RawStub>(f);
i
}