#![allow(clippy::as_conversions, reason = "address is only used as an heuristic to retrieve locks")]
use crate::misc::{CachePadded, sync::seq_lock::SeqLock};
use core::{
cell::UnsafeCell,
fmt::{Debug, Formatter},
mem,
panic::{RefUnwindSafe, UnwindSafe},
ptr,
};
const LEN: usize = 67;
static LOCKS: [CachePadded<SeqLock>; LEN] = [const { CachePadded(SeqLock::new()) }; LEN];
pub struct AtomicCell<T> {
value: UnsafeCell<T>,
}
impl<T> AtomicCell<T> {
#[inline]
pub const fn new(value: T) -> AtomicCell<T> {
AtomicCell { value: UnsafeCell::new(value) }
}
#[inline]
pub fn into_inner(self) -> T {
self.value.into_inner()
}
#[inline]
pub fn load(&self) -> T
where
T: Copy,
{
let src = self.as_ptr();
let lock = lock(src as usize);
if let Some(stamp) = lock.optimistic_read() {
let value = unsafe { ptr::read_volatile(src) };
if lock.validate_read(stamp) {
return value;
}
}
let guard = lock.write();
let value = unsafe { ptr::read(src) };
guard.abort();
value
}
#[inline]
pub fn store(&self, value: T) {
if const { mem::needs_drop::<T>() } {
drop(self.swap(value));
} else {
let dst = self.as_ptr();
let _guard = lock(dst as usize).write();
unsafe {
ptr::write(dst, value);
}
}
}
#[inline]
pub fn swap(&self, value: T) -> T {
let dst = self.value.get();
let _guard = lock(dst as usize).write();
unsafe { ptr::replace(dst, value) }
}
#[inline]
fn as_ptr(&self) -> *mut T {
self.value.get()
}
}
impl<T> Debug for AtomicCell<T>
where
T: Copy + Debug,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AtomicCell").field("value", &self.load()).finish()
}
}
impl<T> Default for AtomicCell<T>
where
T: Default,
{
#[inline]
fn default() -> AtomicCell<T> {
AtomicCell::new(T::default())
}
}
impl<T> From<T> for AtomicCell<T> {
#[inline]
fn from(value: T) -> AtomicCell<T> {
AtomicCell::new(value)
}
}
impl<T> RefUnwindSafe for AtomicCell<T> {}
unsafe impl<T> Send for AtomicCell<T> where T: Send {}
unsafe impl<T: Send> Sync for AtomicCell<T> {}
impl<T> UnwindSafe for AtomicCell<T> where T: Send {}
#[inline]
fn lock(addr: usize) -> &'static SeqLock {
#[allow(clippy::indexing_slicing, reason = "modulo result will always be in-bounds")]
&LOCKS[addr % LEN].0
}