use std::cell::Cell;
use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use super::RefCnt;
const DEBT_SLOT_CNT: usize = 8;
pub(crate) struct Debt(AtomicUsize);
impl Default for Debt {
fn default() -> Self {
Debt(AtomicUsize::new(NO_DEBT))
}
}
#[repr(align(64))]
#[derive(Default)]
struct Slots([Debt; DEBT_SLOT_CNT]);
#[repr(C)]
struct Node {
slots: Slots,
next: Option<&'static Node>,
in_use: AtomicBool,
}
impl Default for Node {
fn default() -> Self {
Node {
next: None,
in_use: AtomicBool::new(true),
slots: Default::default(),
}
}
}
impl Node {
fn get() -> &'static Self {
traverse(|node| {
if !node.in_use.compare_and_swap(false, true, Ordering::Relaxed) {
Some(node)
} else {
None
}
})
.unwrap_or_else(|| {
let node = Box::leak(Box::new(Node::default()));
node.in_use.store(true, Ordering::Relaxed);
let mut head = DEBT_HEAD.load(Ordering::Relaxed);
loop {
node.next = unsafe { head.as_ref() };
if let Err(old) = DEBT_HEAD.compare_exchange_weak(
head,
node,
Ordering::AcqRel,
Ordering::Relaxed, ) {
head = old;
} else {
return node;
}
}
})
}
}
pub(crate) const NO_DEBT: usize = 1;
static DEBT_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
struct DebtHead {
node: Cell<Option<&'static Node>>,
offset: Cell<usize>,
}
impl Drop for DebtHead {
fn drop(&mut self) {
if let Some(node) = self.node.get() {
assert!(node.in_use.swap(false, Ordering::Relaxed));
}
}
}
thread_local! {
static THREAD_HEAD: DebtHead = DebtHead {
node: Cell::new(None),
offset: Cell::new(0),
};
}
fn traverse<R, F: FnMut(&'static Node) -> Option<R>>(mut f: F) -> Option<R> {
let mut current = unsafe { DEBT_HEAD.load(Ordering::Acquire).as_ref() };
while let Some(node) = current {
let result = f(node);
if result.is_some() {
return result;
}
current = node.next;
}
None
}
impl Debt {
#[allow(unknown_lints, new_ret_no_self)]
#[inline]
pub(crate) fn new(ptr: usize) -> Option<&'static Self> {
THREAD_HEAD
.try_with(|head| {
let node = match head.node.get() {
Some(node) => node,
None => {
let new_node = Node::get();
head.node.set(Some(new_node));
new_node
}
};
debug_assert!(node.in_use.load(Ordering::Relaxed));
let offset = head.offset.get();
let len = node.slots.0.len();
for i in 0..len {
let i = (i + offset) % len;
let got_it = node.slots.0[i]
.0
.compare_exchange(NO_DEBT, ptr, Ordering::SeqCst, Ordering::Relaxed)
.is_ok();
if got_it {
head.offset.set(i + 1);
return Some(&node.slots.0[i]);
}
}
None
})
.ok()
.and_then(|new| new)
}
#[inline]
pub(crate) fn pay<T: RefCnt>(&self, ptr: *const T::Base) -> bool {
self.0
.compare_exchange(ptr as usize, NO_DEBT, Ordering::Release, Ordering::Relaxed)
.is_ok()
}
pub(crate) fn pay_all<T: RefCnt>(ptr: *const T::Base) {
let val = unsafe { T::from_ptr(ptr) };
T::inc(&val);
traverse::<(), _>(|node| {
for slot in &node.slots.0 {
if slot
.0
.compare_exchange(ptr as usize, NO_DEBT, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
T::inc(&val);
}
}
None
});
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
#[test]
fn arc_zst() {
struct A;
struct B;
let a = Arc::new(A);
let b = Arc::new(B);
let aref: &A = &a;
let bref: &B = &b;
let aptr = aref as *const _ as usize;
let bptr = bref as *const _ as usize;
assert_ne!(aptr, bptr);
}
}