use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::usize;
use std::fmt;
use std::sync::Mutex;
use crate::krse::cell::CausalCell;
use crate::krse::io::{self, Pack};
#[cfg(target_pointer_width = "64")]
const MAX_THREADS: usize = 4096;
#[cfg(target_pointer_width = "32")]
const MAX_THREADS: usize = 2048;
const MAX_PAGES: usize = io::pointer_width() as usize / 4;
const INITIAL_PAGE_SIZE: usize = 32;
pub(crate) struct Slab<T> {
shard: Shard<T>,
local: Mutex<()>,
}
unsafe impl<T: Send> Send for Slab<T> {}
unsafe impl<T: Sync> Sync for Slab<T> {}
impl<T: Entry> Slab<T> {
pub(crate) fn new() -> Slab<T> {
Slab {
shard: Shard::new(),
local: Mutex::new(()),
}
}
pub(crate) fn alloc(&self) -> Option<Address> {
let _local = self.local.lock().unwrap();
self.shard.alloc()
}
pub(crate) fn remove(&self, idx: Address) {
let lock = self.local.try_lock();
if lock.is_ok() {
self.shard.remove_local(idx)
} else {
self.shard.remove_remote(idx)
}
}
pub(crate) fn get(&self, token: Address) -> Option<&T> {
self.shard.get(token)
}
}
impl<T> fmt::Debug for Slab<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Slab")
.field("shard", &self.shard)
.finish()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct Address(usize);
const PAGE_INDEX_SHIFT: u32 = INITIAL_PAGE_SIZE.trailing_zeros() + 1;
const SLOT: Pack = Pack::least_significant(
MAX_PAGES as u32 + PAGE_INDEX_SHIFT);
const THREAD: Pack = SLOT.then(MAX_THREADS.trailing_zeros() + 1);
const GENERATION: Pack = THREAD.then(
io::pointer_width().wrapping_sub(RESERVED.width() + THREAD.width() + SLOT.width()));
const RESERVED: Pack = Pack::most_significant(5);
impl Address {
pub(crate) const NULL: usize = usize::MAX >> 1;
pub(super) const GENERATION_WIDTH: u32 = GENERATION.width();
pub(super) fn new(shard_index: usize, generation: Generation) -> Address {
let mut repr = 0;
repr = SLOT.pack(shard_index, repr);
repr = GENERATION.pack(generation.to_usize(), repr);
Address(repr)
}
pub(crate) fn from_usize(src: usize) -> Address {
assert_ne!(src, Self::NULL);
Address(src)
}
pub(crate) fn to_usize(self) -> usize {
self.0
}
pub(crate) fn generation(self) -> Generation {
Generation::new(GENERATION.unpack(self.0))
}
pub(super) fn page(self) -> usize {
let slot_shifted = (self.slot() + INITIAL_PAGE_SIZE) >> PAGE_INDEX_SHIFT;
(io::pointer_width() - slot_shifted.leading_zeros()) as usize
}
pub(super) fn slot(self) -> usize {
SLOT.unpack(self.0)
}
}
pub(crate) trait Entry: Default {
fn generation(&self) -> Generation;
fn reset(&self, generation: Generation) -> bool;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub(crate) struct Generation(usize);
impl Generation {
pub(crate) const WIDTH: u32 = Address::GENERATION_WIDTH;
pub(super) const MAX: usize = io::mask_for(Address::GENERATION_WIDTH);
pub(crate) fn new(value: usize) -> Generation {
assert!(value <= Self::MAX);
Generation(value)
}
pub(crate) fn next(self) -> Generation {
Generation((self.0 + 1) & Self::MAX)
}
pub(crate) fn to_usize(self) -> usize {
self.0
}
}
pub(super) struct TransferStack {
head: AtomicUsize,
}
impl TransferStack {
pub(super) fn new() -> Self {
Self {
head: AtomicUsize::new(Address::NULL),
}
}
pub(super) fn pop_all(&self) -> Option<usize> {
let val = self.head.swap(Address::NULL, Ordering::Acquire);
if val == Address::NULL {
None
} else {
Some(val)
}
}
pub(super) fn push(&self, value: usize, before: impl Fn(usize)) {
let mut next = self.head.load(Ordering::Relaxed);
loop {
before(next);
match self
.head
.compare_exchange(next, value, Ordering::AcqRel, Ordering::Acquire)
{
Err(actual) => next = actual,
Ok(_) => return,
}
}
}
}
impl fmt::Debug for TransferStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransferStack")
.field(
"head",
&format_args!("{:#x}", self.head.load(Ordering::Relaxed)),
)
.finish()
}
}
pub(super) struct Shard<T> {
local: Box<[Local]>,
shared: Box<[Shared<T>]>,
}
impl<T: Entry> Shard<T> {
pub(super) fn new() -> Shard<T> {
let mut total_sz = 0;
let shared = (0..MAX_PAGES)
.map(|page_num| {
let sz = page_size(page_num);
let prev_sz = total_sz;
total_sz += sz;
Shared::new(sz, prev_sz)
})
.collect();
let local = (0..MAX_PAGES).map(|_| Local::new()).collect();
Shard {
local,
shared,
}
}
pub(super) fn alloc(&self) -> Option<Address> {
for (page_idx, page) in self.shared.iter().enumerate() {
let local = self.local(page_idx);
if let Some(page_offset) = page.alloc(local) {
return Some(page_offset);
}
}
None
}
pub(super) fn get(&self, addr: Address) -> Option<&T> {
let page_idx = addr.page();
if page_idx > self.shared.len() {
return None;
}
self.shared[page_idx].get(addr)
}
pub(super) fn remove_local(&self, addr: Address) {
let page_idx = addr.page();
if let Some(page) = self.shared.get(page_idx) {
page.remove_local(self.local(page_idx), addr);
}
}
pub(super) fn remove_remote(&self, addr: Address) {
if let Some(page) = self.shared.get(addr.page()) {
page.remove_remote(addr);
}
}
fn local(&self, i: usize) -> &Local {
&self.local[i]
}
}
impl<T> fmt::Debug for Shard<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Shard")
.field("shared", &self.shared)
.finish()
}
}
pub(super) struct Slot<T> {
next: CausalCell<usize>,
entry: T,
}
impl<T: Entry> Slot<T> {
pub(super) fn new(next: usize) -> Slot<T> {
Slot {
next: CausalCell::new(next),
entry: T::default(),
}
}
pub(super) fn get(&self) -> &T {
&self.entry
}
pub(super) fn generation(&self) -> Generation {
self.entry.generation()
}
pub(super) fn reset(&self, generation: Generation) -> bool {
self.entry.reset(generation)
}
pub(super) fn next(&self) -> usize {
self.next.with(|next| unsafe { *next })
}
pub(super) fn set_next(&self, next: usize) {
self.next.with_mut(|n| unsafe {
(*n) = next;
})
}
}
pub(crate) struct Local {
head: CausalCell<usize>,
}
pub(crate) struct Shared<T> {
remote: TransferStack,
size: usize,
prev_sz: usize,
slab: CausalCell<Option<Box<[Slot<T>]>>>,
}
pub(super) fn page_size(n: usize) -> usize {
INITIAL_PAGE_SIZE << n
}
impl Local {
pub(crate) fn new() -> Self {
Self {
head: CausalCell::new(0),
}
}
fn head(&self) -> usize {
self.head.with(|head| unsafe { *head })
}
fn set_head(&self, new_head: usize) {
self.head.with_mut(|head| unsafe {
*head = new_head;
})
}
}
impl<T: Entry> Shared<T> {
pub(crate) fn new(size: usize, prev_sz: usize) -> Shared<T> {
Self {
prev_sz,
size,
remote: TransferStack::new(),
slab: CausalCell::new(None),
}
}
#[cold]
fn alloc_page(&self, _: &Local) {
debug_assert!(self.slab.with(|s| unsafe { (*s).is_none() }));
let mut slab = Vec::with_capacity(self.size);
slab.extend((1..self.size).map(Slot::new));
slab.push(Slot::new(Address::NULL));
self.slab.with_mut(|s| {
unsafe {
*s = Some(slab.into_boxed_slice());
}
});
}
pub(crate) fn alloc(&self, local: &Local) -> Option<Address> {
let head = local.head();
let head = if head < self.size {
head
} else {
self.remote.pop_all()?
};
if head == Address::NULL {
return None;
}
let page_needs_alloc = self.slab.with(|s| unsafe { (*s).is_none() });
if page_needs_alloc {
self.alloc_page(local);
}
let gen = self.slab.with(|slab| {
let slab = unsafe { &*(slab) }
.as_ref()
.expect("page must have been allocated to alloc!");
let slot = &slab[head];
local.set_head(slot.next());
slot.generation()
});
let index = head + self.prev_sz;
Some(Address::new(index, gen))
}
pub(crate) fn get(&self, addr: Address) -> Option<&T> {
let page_offset = addr.slot() - self.prev_sz;
self.slab
.with(|slab| unsafe { &*slab }.as_ref()?.get(page_offset))
.map(|slot| slot.get())
}
pub(crate) fn remove_local(&self, local: &Local, addr: Address) {
let offset = addr.slot() - self.prev_sz;
self.slab.with(|slab| {
let slab = unsafe { &*slab }.as_ref();
let slot = if let Some(slot) = slab.and_then(|slab| slab.get(offset)) {
slot
} else {
return;
};
if slot.reset(addr.generation()) {
slot.set_next(local.head());
local.set_head(offset);
}
})
}
pub(crate) fn remove_remote(&self, addr: Address) {
let offset = addr.slot() - self.prev_sz;
self.slab.with(|slab| {
let slab = unsafe { &*slab }.as_ref();
let slot = if let Some(slot) = slab.and_then(|slab| slab.get(offset)) {
slot
} else {
return;
};
if !slot.reset(addr.generation()) {
return;
}
self.remote.push(offset, |next| slot.set_next(next));
})
}
}
impl fmt::Debug for Local {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.head.with(|head| {
let head = unsafe { *head };
f.debug_struct("Local")
.field("head", &format_args!("{:#0x}", head))
.finish()
})
}
}
impl<T> fmt::Debug for Shared<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Shared")
.field("remote", &self.remote)
.field("prev_sz", &self.prev_sz)
.field("size", &self.size)
.finish()
}
}