use std::cell::Cell;
use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use super::RefCnt;
const DEBT_SLOT_CNT: usize = 6;
pub(crate) struct Debt(AtomicUsize);
impl Default for Debt {
fn default() -> Self {
Debt(AtomicUsize::new(NO_DEBT))
}
}
#[repr(align(64))]
struct Node {
next: Option<&'static Node>,
in_use: AtomicBool,
slots: [Debt; DEBT_SLOT_CNT],
}
impl Default for Node {
fn default() -> Self {
Node {
next: None,
in_use: AtomicBool::new(true),
slots: Default::default(),
}
}
}
pub(crate) const NO_DEBT: usize = 1;
static DEBT_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
struct DebtHead(Cell<Option<&'static Node>>);
impl Drop for DebtHead {
fn drop(&mut self) {
if let Some(node) = self.0.get() {
assert!(node.in_use.swap(false, Ordering::Relaxed));
}
}
}
thread_local! {
static THREAD_HEAD: DebtHead = DebtHead(Cell::new(None));
}
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)]
pub(crate) fn new(ptr: usize) -> Option<&'static Self> {
THREAD_HEAD
.try_with(|head| {
if let Some(node) = head.0.get() {
return node;
}
let new_node = 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(
head,
node,
Ordering::Release, Ordering::Relaxed, ) {
head = old;
} else {
return node;
}
}
});
head.0.set(Some(new_node));
new_node
})
.ok()
.and_then(|node| {
debug_assert!(node.in_use.load(Ordering::Relaxed)); node.slots.iter().find(|slot| {
slot.0
.compare_exchange(NO_DEBT, ptr, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
})
})
}
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 {
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);
}
}