use crate::{Backoff, CacheAligned};
use alloc::{borrow::Cow, boxed::Box};
use core::{
cell::Cell,
fmt,
marker::PhantomData,
ptr::NonNull,
sync::atomic::{
self, AtomicBool, AtomicU32, AtomicUsize,
Ordering::{Acquire, Relaxed, Release, SeqCst},
},
};
const PINNED_BIT: u32 = 1 << 0;
#[cfg(not(miri))]
pub(crate) const PINNINGS_BETWEEN_ADVANCE: usize = 128;
#[cfg(miri)]
pub(crate) const PINNINGS_BETWEEN_ADVANCE: usize = 4;
pub struct GlobalHandle {
ptr: NonNull<Global>,
}
unsafe impl Send for GlobalHandle {}
unsafe impl Sync for GlobalHandle {}
impl Default for GlobalHandle {
fn default() -> Self {
Self::new()
}
}
impl GlobalHandle {
#[must_use]
pub fn new() -> Self {
Global::register()
}
#[inline]
#[must_use]
pub fn register_local(&self) -> UniqueLocalHandle {
Local::register(self)
}
#[inline]
pub(crate) fn epoch(&self) -> u32 {
self.global().epoch.load(Relaxed)
}
#[inline]
fn global(&self) -> &Global {
unsafe { self.ptr.as_ref() }
}
}
impl Clone for GlobalHandle {
#[inline]
fn clone(&self) -> Self {
#[allow(clippy::cast_sign_loss)]
if self.global().handle_count.fetch_add(1, Relaxed) > isize::MAX as usize {
abort();
}
unsafe { GlobalHandle { ptr: self.ptr } }
}
}
impl fmt::Debug for GlobalHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalHandle").finish_non_exhaustive()
}
}
impl PartialEq for GlobalHandle {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
}
impl Eq for GlobalHandle {}
impl Drop for GlobalHandle {
#[inline]
fn drop(&mut self) {
if self.global().handle_count.fetch_sub(1, Release) == 1 {
unsafe { Global::unregister(self.ptr) };
}
}
}
pub struct UniqueLocalHandle {
inner: LocalHandle,
}
impl UniqueLocalHandle {
#[inline]
#[must_use]
pub fn pin(&self) -> Guard<'_> {
self.inner.pin()
}
#[inline]
#[must_use]
pub fn global(&self) -> &GlobalHandle {
self.inner.global()
}
#[inline]
#[must_use]
pub fn into_inner(self) -> LocalHandle {
self.inner
}
}
unsafe impl Send for UniqueLocalHandle {}
impl fmt::Debug for UniqueLocalHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UniqueLocalHandle").finish_non_exhaustive()
}
}
pub struct LocalHandle {
ptr: NonNull<Local>,
}
impl LocalHandle {
#[allow(clippy::missing_panics_doc)]
#[inline]
#[must_use]
pub fn pin(&self) -> Guard<'static> {
let local = self.local();
let global = local.global();
let guard_count = local.guard_count.get();
local.guard_count.set(guard_count.checked_add(1).unwrap());
if guard_count == 0 {
let global_epoch = global.epoch.load(Relaxed);
let new_epoch = global_epoch | PINNED_BIT;
local.epoch.store(new_epoch, Relaxed);
atomic::fence(SeqCst);
local.pin_count.set(local.pin_count.get().wrapping_add(1));
if local.pin_count.get() % PINNINGS_BETWEEN_ADVANCE == 0 {
global.try_advance();
}
}
unsafe { Guard::new(self.ptr) }
}
#[inline]
#[must_use]
pub fn global(&self) -> &GlobalHandle {
&self.local().global
}
#[inline]
fn local(&self) -> &Local {
unsafe { self.ptr.as_ref() }
}
}
impl Clone for LocalHandle {
#[inline]
fn clone(&self) -> Self {
let local = self.local();
local.handle_count.set(local.handle_count.get() + 1);
unsafe { LocalHandle { ptr: self.ptr } }
}
}
impl fmt::Debug for LocalHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalHandle").finish_non_exhaustive()
}
}
impl Drop for LocalHandle {
#[inline]
fn drop(&mut self) {
let local = self.local();
unsafe { local.handle_count.set(local.handle_count.get() - 1) };
if local.handle_count.get() == 0 && local.guard_count.get() == 0 {
unsafe { Local::unregister(self.ptr) };
}
}
}
pub struct Guard<'a> {
local: NonNull<Local>,
marker: PhantomData<&'a ()>,
}
impl Guard<'_> {
unsafe fn new(local: NonNull<Local>) -> Self {
Guard {
local,
marker: PhantomData,
}
}
#[inline]
#[must_use]
pub fn global(&self) -> &GlobalHandle {
&self.local().global
}
#[allow(clippy::must_use_candidate)]
#[inline]
pub fn try_advance_global(&self) -> bool {
let local = self.local();
local.pin_count.set(0);
local.global().try_advance()
}
#[inline]
fn local(&self) -> &Local {
unsafe { self.local.as_ref() }
}
}
impl Clone for Guard<'_> {
#[inline]
fn clone(&self) -> Self {
let local = self.local();
let guard_count = local.guard_count.get();
local.guard_count.set(guard_count.checked_add(1).unwrap());
unsafe { Guard::new(self.local) }
}
}
impl fmt::Debug for Guard<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Guard").finish_non_exhaustive()
}
}
impl Drop for Guard<'_> {
#[inline]
fn drop(&mut self) {
let local = self.local();
unsafe { local.guard_count.set(local.guard_count.get() - 1) };
if local.guard_count.get() == 0 {
unsafe { local.epoch.store(0, Release) };
if local.handle_count.get() == 0 {
unsafe { Local::unregister(self.local) };
}
}
}
}
impl<'a> From<&'a Guard<'a>> for Cow<'a, Guard<'a>> {
#[inline]
fn from(guard: &'a Guard<'a>) -> Self {
Cow::Borrowed(guard)
}
}
impl<'a> From<Guard<'a>> for Cow<'_, Guard<'a>> {
#[inline]
fn from(guard: Guard<'a>) -> Self {
Cow::Owned(guard)
}
}
#[repr(C)]
struct Global {
local_list_head: Cell<Option<NonNull<Local>>>,
local_list_lock: AtomicBool,
handle_count: AtomicUsize,
_alignment: CacheAligned,
epoch: AtomicU32,
}
unsafe impl Sync for Global {}
impl Global {
fn register() -> GlobalHandle {
let global = Box::new(Global {
local_list_head: Cell::new(None),
local_list_lock: AtomicBool::new(false),
handle_count: AtomicUsize::new(1),
_alignment: CacheAligned,
epoch: AtomicU32::new(0),
});
let ptr = unsafe { NonNull::new_unchecked(Box::into_raw(global)) };
unsafe { GlobalHandle { ptr } }
}
#[inline(never)]
unsafe fn unregister(global: NonNull<Global>) {
unsafe { global.as_ref() }.handle_count.load(Acquire);
let _ = unsafe { Box::from_raw(global.as_ptr()) };
}
fn lock_local_list(&self) {
let mut backoff = Backoff::new();
loop {
match self
.local_list_lock
.compare_exchange_weak(false, true, Acquire, Relaxed)
{
Ok(_) => break,
Err(_) => backoff.spin(),
}
}
}
fn try_lock_local_list(&self) -> bool {
self.local_list_lock
.compare_exchange(false, true, Acquire, Relaxed)
.is_ok()
}
unsafe fn unlock_local_list(&self) {
self.local_list_lock.store(false, Release);
}
#[inline(never)]
fn try_advance(&self) -> bool {
let global_epoch = self.epoch.load(Relaxed);
atomic::fence(SeqCst);
if !self.try_lock_local_list() {
return false;
}
let mut head = self.local_list_head.get();
while let Some(local) = head {
let local = unsafe { local.as_ref() };
let local_epoch = local.epoch.load(Relaxed);
if local_epoch & PINNED_BIT != 0 && local_epoch & !PINNED_BIT != global_epoch {
unsafe { self.unlock_local_list() };
return false;
}
head = local.next.get();
}
unsafe { self.unlock_local_list() };
let new_epoch = global_epoch.wrapping_add(2);
atomic::fence(Acquire);
self.epoch.store(new_epoch, Release);
true
}
}
#[repr(C)]
struct Local {
next: Cell<Option<NonNull<Self>>>,
prev: Cell<Option<NonNull<Self>>>,
epoch: AtomicU32,
_alignment: CacheAligned,
global: GlobalHandle,
handle_count: Cell<usize>,
guard_count: Cell<usize>,
pin_count: Cell<usize>,
}
impl Local {
#[inline(never)]
fn register(global: &GlobalHandle) -> UniqueLocalHandle {
let mut local = Box::new(Local {
next: Cell::new(None),
prev: Cell::new(None),
epoch: AtomicU32::new(0),
_alignment: CacheAligned,
global: global.clone(),
handle_count: Cell::new(1),
guard_count: Cell::new(0),
pin_count: Cell::new(0),
});
let global = global.global();
global.lock_local_list();
let head = global.local_list_head.get();
local.next = Cell::new(head);
let ptr = unsafe { NonNull::new_unchecked(Box::into_raw(local)) };
global.local_list_head.set(Some(ptr));
if let Some(head) = head {
unsafe { head.as_ref() }.prev.set(Some(ptr));
}
unsafe { global.unlock_local_list() };
let handle = unsafe { LocalHandle { ptr } };
unsafe { UniqueLocalHandle { inner: handle } }
}
#[inline(never)]
unsafe fn unregister(ptr: NonNull<Self>) {
let local = unsafe { ptr.as_ref() };
let global = local.global.global();
global.lock_local_list();
if let Some(prev) = local.prev.get() {
unsafe { prev.as_ref() }.next.set(local.next.get());
} else {
global.local_list_head.set(local.next.get());
}
if let Some(next) = local.next.get() {
unsafe { next.as_ref() }.prev.set(local.prev.get());
}
unsafe { global.unlock_local_list() };
let _ = unsafe { Box::from_raw(ptr.as_ptr()) };
}
#[inline]
fn global(&self) -> &Global {
self.global.global()
}
}
#[cold]
fn abort() -> ! {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!();
}
}
let _p = PanicOnDrop;
panic!();
}