use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub(crate) struct Tag(u8);
impl Tag {
pub(crate) const FROZEN: Self = Self::new(u8::MAX);
pub(crate) const PUBLISHED: Self = Self::new(u8::MAX - 1);
pub(crate) const AVAILABLE: Self = Self::new(0);
pub(crate) const OWNED: Self = Self::new(1);
pub(crate) const RETIRING: Self = Self::new(2);
const RESERVED: Self = Self::RETIRING;
#[must_use = "this function has no side-effects"]
pub(crate) fn is_reserved(self) -> bool {
(self <= Self::RESERVED) || (self == Self::FROZEN)
}
#[must_use = "this function has no side-effects"]
pub(crate) fn can_read(self) -> bool {
self >= Self::PUBLISHED
}
#[inline]
const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
const fn value(self) -> u8 {
self.0
}
}
impl std::fmt::Display for Tag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let me = *self;
if me == Self::AVAILABLE {
f.write_str("Tag(AVAILABLE)")
} else if me == Self::OWNED {
f.write_str("Tag(OWNED)")
} else if me == Self::RETIRING {
f.write_str("Tag(RETIRING)")
} else if me == Self::FROZEN {
f.write_str("Tag(FROZEN)")
} else if me == Self::PUBLISHED {
f.write_str("Tag(PUBLISHED)")
} else {
write!(f, "Tag({})", me.value())
}
}
}
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct AtomicTag(AtomicU8);
impl AtomicTag {
pub(crate) const fn new(tag: Tag) -> Self {
Self(AtomicU8::new(tag.value()))
}
pub(crate) unsafe fn from_ptr<'a>(ptr: *mut AtomicTag) -> &'a Self {
unsafe { &*ptr }
}
pub(crate) fn compare_exchange(
&self,
current: Tag,
new: Tag,
success: Ordering,
failure: Ordering,
) -> Result<Tag, Tag> {
self.0
.compare_exchange(current.value(), new.value(), success, failure)
.map(Tag::new)
.map_err(Tag::new)
}
pub(crate) fn load(&self, ordering: Ordering) -> Tag {
Tag::new(self.0.load(ordering))
}
pub(crate) fn store(&self, val: Tag, ordering: Ordering) {
self.0.store(val.value(), ordering)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::Barrier, thread};
use crate::{
buffer::Buffer,
num::{Align, Bytes},
};
fn spin_decrement(m: &AtomicTag, count: usize) {
for _ in 0..count {
let mut current = m.load(Ordering::Relaxed);
while let Err(c) = m.compare_exchange(
current,
Tag::new(current.value().wrapping_sub(1)),
Ordering::Relaxed,
Ordering::Relaxed,
) {
current = c;
}
}
}
#[test]
fn test_atomic() {
let threads = 4;
let barrier = &Barrier::new(threads);
let buffer =
Buffer::new(1, Bytes::size_of::<AtomicTag>(), Align::of::<AtomicTag>()).unwrap();
let ptr = buffer.get(0).unwrap().as_mut_ptr().cast::<AtomicTag>();
{
let tag = unsafe { AtomicTag::from_ptr(ptr) };
tag.store(Tag::FROZEN, Ordering::Relaxed);
}
let count = 1000;
thread::scope(|s| {
for _ in 0..threads {
s.spawn(|| {
let p = buffer.get(0).unwrap().as_mut_ptr().cast::<AtomicTag>();
let tag = unsafe { AtomicTag::from_ptr(p) };
barrier.wait();
spin_decrement(tag, count);
});
}
});
{
let g = unsafe { AtomicTag::from_ptr(ptr) }.load(Ordering::Relaxed);
assert_eq!(g, Tag::new(u8::MAX.wrapping_sub((count * threads) as u8)));
}
}
#[test]
fn test_is_reserved() {
assert!(Tag::FROZEN.is_reserved());
assert!(!Tag::PUBLISHED.is_reserved());
assert!(Tag::AVAILABLE.is_reserved());
assert!(Tag::OWNED.is_reserved());
assert!(Tag::RETIRING.is_reserved());
}
#[test]
fn test_can_read() {
assert!(Tag::FROZEN.can_read());
assert!(Tag::PUBLISHED.can_read());
assert!(!Tag::AVAILABLE.can_read());
assert!(!Tag::OWNED.can_read());
assert!(!Tag::RETIRING.can_read());
}
#[test]
fn test_display() {
assert_eq!(Tag::AVAILABLE.to_string(), "Tag(AVAILABLE)");
assert_eq!(Tag::OWNED.to_string(), "Tag(OWNED)");
assert_eq!(Tag::RETIRING.to_string(), "Tag(RETIRING)");
assert_eq!(Tag::FROZEN.to_string(), "Tag(FROZEN)");
assert_eq!(Tag::PUBLISHED.to_string(), "Tag(PUBLISHED)");
assert_eq!(Tag::new(Tag::RETIRING.value() + 1).to_string(), "Tag(3)");
assert_eq!(Tag::new(Tag::PUBLISHED.value() - 1).to_string(), "Tag(253)");
}
}