use super::{
cache_padded::CachePadded,
sys::{
hint,
sync::atomic::{AtomicU8, Ordering},
},
};
type AtomicSlots = AtomicU8;
pub type SlotIndex = u32;
#[repr(transparent)]
pub struct Slots(CachePadded<AtomicSlots>);
impl Slots {
pub const SLOTS_SIZE: u32 = u8::BITS;
const ALL_VACANT: u8 = u8::MAX;
pub fn new() -> Self {
Self(CachePadded::new(AtomicSlots::new(
Self::ALL_VACANT,
)))
}
pub fn try_acquire(&self) -> Option<SlotIndex> {
let mut all_slots = self.0.load(Ordering::Relaxed);
loop {
let Some(vacant_slot) = all_slots.isolate_lowest_vacant() else {
return None;
};
let all_slots_with_vacant_turned_occupied = all_slots ^ vacant_slot;
let Err(next_all_slots) = self.0.compare_exchange_weak(
all_slots,
all_slots_with_vacant_turned_occupied,
Ordering::AcqRel,
Ordering::Acquire,
) else {
return Some(vacant_slot.index());
};
all_slots = next_all_slots;
hint::spin_loop();
}
}
pub fn release(&self, slot_index: SlotIndex) {
let occupied_slot = 1 << slot_index;
let mut all_slots = self.0.load(Ordering::Relaxed);
loop {
let all_slots_with_occupied_turned_vacant =
all_slots ^ occupied_slot;
if all_slots & occupied_slot > 0 {
unreachable!("Releasing to a vacant slot is impossible");
}
let Err(next_all_slots) = self.0.compare_exchange_weak(
all_slots,
all_slots_with_occupied_turned_vacant,
Ordering::AcqRel,
Ordering::Acquire,
) else {
return;
};
all_slots = next_all_slots;
hint::spin_loop();
}
}
pub fn is_all_vacants(&self) -> bool {
self.0.load(Ordering::Relaxed) == Self::ALL_VACANT
}
}
trait Vacant {
fn isolate_lowest_vacant(self) -> Option<Self>
where
Self: Sized;
fn index(self) -> u32;
}
impl Vacant for u8 {
#[inline(always)]
fn isolate_lowest_vacant(self) -> Option<Self> {
let lowest_one = self.isolate_lowest_one();
(lowest_one > 0).then_some(lowest_one)
}
#[inline(always)]
fn index(self) -> u32 {
self.trailing_zeros()
}
}
#[cfg(test)]
mod tests {
use super::{super::tests_helper::run, Slots, Vacant};
#[test]
fn test_try_acquire() {
run(|| {
let slots = Slots::new();
assert_eq!(slots.try_acquire(), Some(0));
assert_eq!(slots.try_acquire(), Some(1));
assert_eq!(slots.try_acquire(), Some(2));
assert_eq!(slots.try_acquire(), Some(3));
assert_eq!(slots.try_acquire(), Some(4));
assert_eq!(slots.try_acquire(), Some(5));
assert_eq!(slots.try_acquire(), Some(6));
assert_eq!(slots.try_acquire(), Some(7));
assert!(slots.try_acquire().is_none());
})
}
#[test]
fn test_release() {
run(|| {
let slots = Slots::new();
let slot_0 = slots.try_acquire().unwrap();
assert_eq!(slot_0, 0);
slots.release(slot_0);
let slot_0 = slots.try_acquire().unwrap();
assert_eq!(slot_0, 0);
let slot_1 = slots.try_acquire().unwrap();
assert_eq!(slot_1, 1);
let slot_2 = slots.try_acquire().unwrap();
assert_eq!(slot_2, 2);
let slot_3 = slots.try_acquire().unwrap();
assert_eq!(slot_3, 3);
slots.release(slot_1);
let slot_1 = slots.try_acquire().unwrap();
assert_eq!(slot_1, 1);
let slot_4 = slots.try_acquire().unwrap();
assert_eq!(slot_4, 4);
let slot_5 = slots.try_acquire().unwrap();
assert_eq!(slot_5, 5);
let slot_6 = slots.try_acquire().unwrap();
assert_eq!(slot_6, 6);
let slot_7 = slots.try_acquire().unwrap();
assert_eq!(slot_7, 7);
assert!(slots.try_acquire().is_none());
slots.release(slot_1);
let slot_1 = slots.try_acquire().unwrap();
assert_eq!(slot_1, 1);
slots.release(slot_4);
let slot_4 = slots.try_acquire().unwrap();
assert_eq!(slot_4, 4);
})
}
#[test]
#[should_panic]
fn test_release_twice() {
run(|| {
let slots = Slots::new();
let slot_0 = slots.try_acquire().unwrap();
assert_eq!(slot_0, 0);
slots.release(slot_0);
slots.release(slot_0);
})
}
#[test]
fn test_vacant_trait() {
run(|| {
assert_eq!(
0b0010_1000_u8.isolate_lowest_vacant(),
Some(0b0000_1000)
);
assert_eq!(
0b0010_1000_u8.isolate_lowest_vacant().unwrap().index(),
3
);
assert!(0b0000_0000_u8.isolate_lowest_vacant().is_none());
})
}
}