use core::fmt;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use crate::ptr::Ptr;
use crate::traits::IntoOptionNonNull;
#[cfg(all(target_pointer_width = "64", not(atomic_fallback)))]
mod ptr64;
#[cfg(all(target_pointer_width = "64", not(atomic_fallback)))]
use ptr64::AtomicTaggedPtrImpl;
#[cfg(all(target_pointer_width = "64", not(atomic_fallback)))]
pub use ptr64::TAG_MASK;
#[cfg(all(target_pointer_width = "32", not(atomic_fallback)))]
mod ptr32;
#[cfg(all(target_pointer_width = "32", not(atomic_fallback)))]
use ptr32::AtomicTaggedPtrImpl;
#[cfg(all(target_pointer_width = "32", not(atomic_fallback)))]
pub use ptr32::TAG_MASK;
#[cfg(atomic_fallback)]
mod fallback;
#[cfg(atomic_fallback)]
pub use fallback::TAG_MASK;
#[cfg(atomic_fallback)]
use fallback::AtomicTaggedPtrImpl;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(pub(crate) usize);
impl Tag {
#[inline]
pub const fn new(value: usize) -> Self {
Self(value & TAG_MASK)
}
#[inline]
pub const fn value(self) -> usize {
self.0
}
#[inline]
pub const fn wrapping_add(self, rhs: usize) -> Self {
Self::new(self.0.wrapping_add(rhs))
}
#[inline]
pub const fn max_value() -> Self {
Self(TAG_MASK)
}
}
impl fmt::Debug for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tag({:#X})", self.0)
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<usize> for Tag {
#[inline]
fn from(value: usize) -> Self {
Self::new(value)
}
}
impl From<Tag> for usize {
#[inline]
fn from(tag: Tag) -> usize {
tag.0
}
}
pub type TaggedPtrResult<T> = Result<(Ptr<T>, Tag), (Ptr<T>, Tag)>;
pub(crate) type RawTaggedPtrResult<T> =
Result<(Option<NonNull<T>>, Tag), (Option<NonNull<T>>, Tag)>;
pub struct AtomicTaggedPtr<T> {
inner: AtomicTaggedPtrImpl<T>,
}
unsafe impl<T> Send for AtomicTaggedPtr<T> {}
unsafe impl<T> Sync for AtomicTaggedPtr<T> {}
impl<T> AtomicTaggedPtr<T> {
#[inline]
pub fn new<P>(ptr: P) -> Self
where
P: IntoOptionNonNull<T>,
{
let raw_ptr = ptr.into_option_non_null();
Self {
inner: AtomicTaggedPtrImpl::new(raw_ptr),
}
}
#[inline]
pub fn load(&self, order: Ordering) -> (Ptr<T>, Tag) {
let (raw_ptr, tag) = self.inner.load(order);
(Ptr::new(raw_ptr), tag)
}
#[inline]
pub fn store<P>(&self, ptr: P, tag: Tag, order: Ordering)
where
P: IntoOptionNonNull<T>,
{
self.inner.store(ptr.into_option_non_null(), tag, order);
}
#[inline]
pub fn compare_exchange<P1, P2>(
&self,
current: (P1, Tag),
new: (P2, Tag),
success: Ordering,
failure: Ordering,
) -> TaggedPtrResult<T>
where
P1: IntoOptionNonNull<T>,
P2: IntoOptionNonNull<T>,
{
match self.inner.compare_exchange(
(current.0.into_option_non_null(), current.1),
(new.0.into_option_non_null(), new.1),
success,
failure,
) {
Ok((raw_ptr, tag)) => Ok((Ptr::new(raw_ptr), tag)),
Err((raw_ptr, tag)) => Err((Ptr::new(raw_ptr), tag)),
}
}
#[inline]
pub fn compare_exchange_weak<P1, P2>(
&self,
current: (P1, Tag),
new: (P2, Tag),
success: Ordering,
failure: Ordering,
) -> TaggedPtrResult<T>
where
P1: IntoOptionNonNull<T>,
P2: IntoOptionNonNull<T>,
{
match self.inner.compare_exchange_weak(
(current.0.into_option_non_null(), current.1),
(new.0.into_option_non_null(), new.1),
success,
failure,
) {
Ok((raw_ptr, tag)) => Ok((Ptr::new(raw_ptr), tag)),
Err((raw_ptr, tag)) => Err((Ptr::new(raw_ptr), tag)),
}
}
}
impl<T> Default for AtomicTaggedPtr<T> {
#[inline]
fn default() -> Self {
Self::new(None)
}
}
impl<T> fmt::Debug for AtomicTaggedPtr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (ptr, tag) = self.load(Ordering::Relaxed);
f.debug_struct("AtomicTaggedPtr")
.field("pointer", &ptr)
.field("tag", &tag)
.finish()
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use std::format;
#[test]
fn test_default_initializer() {
let atom: AtomicTaggedPtr<i32> = Default::default();
let (ptr, tag) = atom.load(Ordering::Relaxed);
assert!(ptr.is_none());
assert_eq!(tag, Tag::new(0));
}
#[test]
fn test_debug_formatter() {
let val = 12345;
let ptr = NonNull::new(&val as *const i32 as *mut i32);
let atom = AtomicTaggedPtr::new(ptr);
atom.store(ptr, Tag::new(88), Ordering::Relaxed);
let debug_str = format!("{:?}", atom);
assert!(debug_str.contains("AtomicTaggedPtr"));
assert!(debug_str.contains("tag: Tag(0x58)"));
}
#[test]
fn test_multithreaded_atomic_exchanges() {
use std::sync::Arc;
use std::thread;
let val = 777;
let ptr = NonNull::new(&val as *const i32 as *mut i32);
let ptr_usize = ptr.unwrap().as_ptr() as usize;
let atom = Arc::new(AtomicTaggedPtr::new(ptr));
let atom_clone = Arc::clone(&atom);
let handle = thread::spawn(move || {
let loaded = atom_clone.load(Ordering::Acquire);
let local_ptr = NonNull::new(ptr_usize as *mut i32);
if loaded.0 == local_ptr && loaded.1 == Tag::new(0) {
let _ = atom_clone.compare_exchange(
(local_ptr, Tag::new(0)),
(None, Tag::new(55)),
Ordering::SeqCst,
Ordering::SeqCst,
);
}
});
handle.join().unwrap();
let final_state = atom.load(Ordering::Acquire);
assert!(final_state.1 == Tag::new(55) || final_state.1 == Tag::new(0));
}
#[test]
fn test_into_option_non_null_api() {
let val1 = 111;
let raw_ptr1 = &val1 as *const i32;
let mut_ptr1 = &val1 as *const i32 as *mut i32;
let non_null1 = NonNull::new(mut_ptr1).unwrap();
let atom = AtomicTaggedPtr::new(non_null1);
assert_eq!(atom.load(Ordering::Relaxed).0.option(), Some(non_null1));
let atom = AtomicTaggedPtr::new(Some(non_null1));
assert_eq!(atom.load(Ordering::Relaxed).0.option(), Some(non_null1));
let atom = AtomicTaggedPtr::new(raw_ptr1);
assert_eq!(atom.load(Ordering::Relaxed).0.option(), Some(non_null1));
let atom = AtomicTaggedPtr::new(mut_ptr1);
assert_eq!(atom.load(Ordering::Relaxed).0.option(), Some(non_null1));
let atom = AtomicTaggedPtr::new(core::ptr::null::<i32>());
assert_eq!(atom.load(Ordering::Relaxed).0.option(), None);
let atom = AtomicTaggedPtr::new(core::ptr::null_mut::<i32>());
assert_eq!(atom.load(Ordering::Relaxed).0.option(), None);
let atom: AtomicTaggedPtr<i32> = AtomicTaggedPtr::new(None);
assert_eq!(atom.load(Ordering::Relaxed).0.option(), None);
let atom = AtomicTaggedPtr::new(None);
atom.store(raw_ptr1, Tag::new(10), Ordering::Relaxed);
let loaded = atom.load(Ordering::Relaxed);
assert_eq!(loaded.0.option(), Some(non_null1));
assert_eq!(loaded.1, Tag::new(10));
atom.store(None, Tag::new(20), Ordering::Relaxed);
let loaded = atom.load(Ordering::Relaxed);
assert_eq!(loaded.0.option(), None);
assert_eq!(loaded.1, Tag::new(20));
let atom = AtomicTaggedPtr::new(raw_ptr1);
let res = atom.compare_exchange(
(raw_ptr1, Tag::new(0)),
(mut_ptr1, Tag::new(1)),
Ordering::SeqCst,
Ordering::SeqCst,
);
assert!(res.is_ok());
let loaded = atom.load(Ordering::Relaxed);
assert_eq!(loaded.0.option(), Some(non_null1));
assert_eq!(loaded.1, Tag::new(1));
let res = atom.compare_exchange_weak(
(mut_ptr1, Tag::new(1)),
(None, Tag::new(2)),
Ordering::SeqCst,
Ordering::SeqCst,
);
let mut res = res;
while res.is_err() {
res = atom.compare_exchange_weak(
(mut_ptr1, Tag::new(1)),
(None, Tag::new(2)),
Ordering::SeqCst,
Ordering::SeqCst,
);
}
assert!(res.is_ok());
let loaded = atom.load(Ordering::Relaxed);
assert_eq!(loaded.0.option(), None);
assert_eq!(loaded.1, Tag::new(2));
}
#[test]
fn test_ptr_conversions() {
let val = 42;
let raw = &val as *const i32;
let mut_ptr = &val as *const i32 as *mut i32;
let non_null = NonNull::new(mut_ptr).unwrap();
let ptr_some = Ptr::new(Some(non_null));
let ptr_none: Ptr<i32> = Ptr::new(None);
assert_eq!(ptr_some.option(), Some(non_null));
assert_eq!(ptr_none.option(), None);
assert_eq!(ptr_some.as_option(), Some(non_null));
assert_eq!(ptr_some.as_ptr(), raw);
assert_eq!(ptr_none.as_ptr(), core::ptr::null());
assert_eq!(ptr_some.as_mut_ptr(), mut_ptr);
assert_eq!(ptr_none.as_mut_ptr(), core::ptr::null_mut());
assert!(ptr_some.is_some());
assert!(!ptr_some.is_null());
assert!(!ptr_some.is_none());
assert!(ptr_none.is_null());
assert!(ptr_none.is_none());
assert!(!ptr_none.is_some());
assert!(ptr_some == Some(non_null));
assert!(ptr_some == non_null);
assert!(ptr_some == raw);
assert!(ptr_some == mut_ptr);
assert!(ptr_none == None);
assert!(ptr_none == core::ptr::null::<i32>());
assert!(ptr_none == core::ptr::null_mut::<i32>());
}
}