use std::cell::Cell;
use std::future::Future;
type ClaimFn = unsafe fn(src: *const u8, size: usize) -> *mut u8;
type TryClaimFn = unsafe fn() -> (*mut u8, usize);
type FreeFn = unsafe fn(ptr: *mut u8);
type ClaimFreeFn = unsafe fn(slab_ptr: *const u8, ptr: *mut u8, chunk_idx: usize);
thread_local! {
static SLAB_PTR: Cell<*const u8> = const { Cell::new(std::ptr::null()) };
static SLAB_CLAIM: Cell<ClaimFn> = const { Cell::new(no_slab_claim) };
static SLAB_FREE: Cell<FreeFn> = const { Cell::new(no_slab_free) };
static SLAB_TRY_CLAIM: Cell<TryClaimFn> = const { Cell::new(no_slab_try_claim) };
static SLAB_CLAIM_FREE: Cell<ClaimFreeFn> = const { Cell::new(no_slab_claim_free) };
static SLAB_SLOT_SIZE: Cell<usize> = const { Cell::new(0) };
}
unsafe fn no_slab_claim(_src: *const u8, _size: usize) -> *mut u8 {
panic!(
"spawn_slab() called without a slab configured — \
use Runtime::builder().slab_unbounded(slab) or .slab_bounded(slab)"
)
}
unsafe fn no_slab_free(_ptr: *mut u8) {
panic!("slab free called without a slab configured")
}
unsafe fn no_slab_try_claim() -> (*mut u8, usize) {
panic!(
"try_claim_slab()/claim_slab() called without a slab configured — \
use Runtime::builder().slab_unbounded(slab) or .slab_bounded(slab)"
)
}
unsafe fn no_slab_claim_free(_slab_ptr: *const u8, _ptr: *mut u8, _chunk_idx: usize) {
panic!("slab claim free called without a slab configured")
}
pub(crate) struct SlabTlsConfig {
pub(crate) slab_ptr: *const u8,
pub(crate) claim_fn: ClaimFn,
pub(crate) free_fn: FreeFn,
pub(crate) try_claim_fn: TryClaimFn,
pub(crate) claim_free_fn: ClaimFreeFn,
pub(crate) slot_size: usize,
}
pub(crate) fn install_slab(slab: Box<dyn std::any::Any>, config: &SlabTlsConfig) -> SlabGuard {
let prev_ptr = SLAB_PTR.with(|c| c.replace(config.slab_ptr));
let prev_claim = SLAB_CLAIM.with(|c| c.replace(config.claim_fn));
let prev_free = SLAB_FREE.with(|c| c.replace(config.free_fn));
let prev_try_claim = SLAB_TRY_CLAIM.with(|c| c.replace(config.try_claim_fn));
let prev_claim_free = SLAB_CLAIM_FREE.with(|c| c.replace(config.claim_free_fn));
let prev_slot_size = SLAB_SLOT_SIZE.with(|c| c.replace(config.slot_size));
SlabGuard {
prev_ptr,
prev_claim,
prev_free,
prev_try_claim,
prev_claim_free,
prev_slot_size,
_slab: slab,
}
}
#[allow(clippy::struct_field_names)]
pub(crate) struct SlabGuard {
prev_ptr: *const u8,
prev_claim: ClaimFn,
prev_free: FreeFn,
prev_try_claim: TryClaimFn,
prev_claim_free: ClaimFreeFn,
prev_slot_size: usize,
_slab: Box<dyn std::any::Any>,
}
impl Drop for SlabGuard {
fn drop(&mut self) {
SLAB_PTR.with(|c| c.set(self.prev_ptr));
SLAB_CLAIM.with(|c| c.set(self.prev_claim));
SLAB_FREE.with(|c| c.set(self.prev_free));
SLAB_TRY_CLAIM.with(|c| c.set(self.prev_try_claim));
SLAB_CLAIM_FREE.with(|c| c.set(self.prev_claim_free));
SLAB_SLOT_SIZE.with(|c| c.set(self.prev_slot_size));
}
}
pub(crate) fn slab_spawn<F>(future: F, tracker_key: u32) -> *mut u8
where
F: Future + 'static,
F::Output: 'static,
{
let cross_wake_ctx = crate::cross_wake::current_runtime_ctx();
let task = crate::task::new_joinable_slab(future, tracker_key, slab_free_task, cross_wake_ctx);
let size = std::mem::size_of_val(&task);
let src = std::ptr::from_ref(&task).cast::<u8>();
let claim = SLAB_CLAIM.with(Cell::get);
let ptr = unsafe { claim(src, size) };
assert!(!ptr.is_null(), "slab full — spawn_slab failed");
std::mem::forget(task);
ptr
}
unsafe fn slab_free_task(ptr: *mut u8) {
let free = SLAB_FREE.with(Cell::get);
unsafe { free(ptr) };
}
pub struct SlabClaim {
ptr: *mut u8,
slab_ptr: *const u8,
free: ClaimFreeFn,
chunk_idx: usize,
slot_size: usize,
_not_send: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl SlabClaim {
pub fn spawn<F>(self, future: F) -> crate::task::JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
crate::runtime::with_executor(|exec| {
let tracker_key = exec.next_tracker_key();
let cross_wake_ctx = crate::cross_wake::current_runtime_ctx();
let task =
crate::task::new_joinable_slab(future, tracker_key, slab_free_task, cross_wake_ctx);
let size = std::mem::size_of_val(&task);
assert!(
size <= self.slot_size,
"task size ({size} bytes) exceeds slab slot size ({} bytes)",
self.slot_size,
);
let src = std::ptr::from_ref(&task).cast::<u8>();
unsafe { std::ptr::copy_nonoverlapping(src, self.ptr, size) };
std::mem::forget(task);
let ptr = self.ptr;
std::mem::forget(self);
exec.spawn_raw(ptr);
crate::task::JoinHandle::new(ptr)
})
}
pub fn as_ptr(&self) -> *mut u8 {
self.ptr
}
pub fn slot_size(&self) -> usize {
self.slot_size
}
}
impl Drop for SlabClaim {
fn drop(&mut self) {
unsafe { (self.free)(self.slab_ptr, self.ptr, self.chunk_idx) };
}
}
impl std::fmt::Debug for SlabClaim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SlabClaim")
.field("ptr", &self.ptr)
.field("slot_size", &self.slot_size)
.finish()
}
}
pub(crate) fn try_claim() -> Option<SlabClaim> {
let try_claim_fn = SLAB_TRY_CLAIM.with(Cell::get);
let (ptr, chunk_idx) = unsafe { try_claim_fn() };
if ptr.is_null() {
return None;
}
let slab_ptr = SLAB_PTR.with(Cell::get);
let free = SLAB_CLAIM_FREE.with(Cell::get);
let slot_size = SLAB_SLOT_SIZE.with(Cell::get);
Some(SlabClaim {
ptr,
slab_ptr,
free,
chunk_idx,
slot_size,
_not_send: std::marker::PhantomData,
})
}
pub(crate) fn claim() -> SlabClaim {
try_claim().expect("slab full — claim_slab failed")
}
pub(crate) fn make_unbounded_config<const S: usize>(slab_ptr: *const u8) -> SlabTlsConfig {
SlabTlsConfig {
slab_ptr,
claim_fn: unbounded_claim::<S>,
free_fn: unbounded_free::<S>,
try_claim_fn: unbounded_try_claim::<S>,
claim_free_fn: unbounded_claim_free::<S>,
slot_size: S,
}
}
pub(crate) fn make_bounded_config<const S: usize>(slab_ptr: *const u8) -> SlabTlsConfig {
SlabTlsConfig {
slab_ptr,
claim_fn: bounded_claim::<S>,
free_fn: bounded_free::<S>,
try_claim_fn: bounded_try_claim::<S>,
claim_free_fn: bounded_claim_free::<S>,
slot_size: S,
}
}
unsafe fn unbounded_claim<const S: usize>(src: *const u8, size: usize) -> *mut u8 {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::unbounded::Slab<S>) };
unsafe { slab.alloc_raw(src, size) }
}
unsafe fn unbounded_free<const S: usize>(ptr: *mut u8) {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::unbounded::Slab<S>) };
let slot = unsafe { nexus_slab::byte::Slot::<u8>::from_raw(ptr) };
slab.free(slot);
}
unsafe fn unbounded_try_claim<const S: usize>() -> (*mut u8, usize) {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::unbounded::Slab<S>) };
let claim = slab.claim();
let ptr = claim.as_ptr();
let chunk_idx = claim.chunk_idx();
std::mem::forget(claim);
(ptr, chunk_idx)
}
unsafe fn unbounded_claim_free<const S: usize>(
slab_ptr: *const u8,
ptr: *mut u8,
chunk_idx: usize,
) {
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::unbounded::Slab<S>) };
unsafe { slab.free_raw_in_chunk(ptr, chunk_idx) };
}
unsafe fn bounded_claim<const S: usize>(src: *const u8, size: usize) -> *mut u8 {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::bounded::Slab<S>) };
unsafe { slab.alloc_raw(src, size) }
}
unsafe fn bounded_free<const S: usize>(ptr: *mut u8) {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::bounded::Slab<S>) };
let slot = unsafe { nexus_slab::byte::Slot::<u8>::from_raw(ptr) };
slab.free(slot);
}
unsafe fn bounded_try_claim<const S: usize>() -> (*mut u8, usize) {
let slab_ptr = SLAB_PTR.with(Cell::get);
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::bounded::Slab<S>) };
slab.try_claim().map_or((std::ptr::null_mut(), 0), |claim| {
let ptr = claim.as_ptr();
std::mem::forget(claim);
(ptr, 0) })
}
unsafe fn bounded_claim_free<const S: usize>(slab_ptr: *const u8, ptr: *mut u8, _chunk_idx: usize) {
let slab = unsafe { &*(slab_ptr as *const nexus_slab::byte::bounded::Slab<S>) };
unsafe { slab.free_raw(ptr) };
}