use crate::retired::INVPTR;
use crate::ttas::TTas;
use alloc::boxed::Box;
use core::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering};
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x"))]
mod native {
use core::sync::atomic::{AtomicU64, Ordering};
use portable_atomic::AtomicU128;
#[cfg(target_endian = "little")]
#[repr(C, align(16))]
pub(crate) struct WordPair {
lo: AtomicU64,
hi: AtomicU64,
}
#[cfg(target_endian = "big")]
#[repr(C, align(16))]
pub(crate) struct WordPair {
hi: AtomicU64,
lo: AtomicU64,
}
impl WordPair {
pub(crate) const fn new(lo: u64, hi: u64) -> Self {
Self {
lo: AtomicU64::new(lo),
hi: AtomicU64::new(hi),
}
}
#[inline]
pub(crate) fn load_lo(&self) -> u64 {
self.lo.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn load_hi(&self) -> u64 {
self.hi.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn load(&self) -> (u64, u64) {
let lo = self.lo.load(Ordering::Acquire);
let hi = self.hi.load(Ordering::Acquire);
(lo, hi)
}
#[inline]
pub(crate) fn store_lo(&self, val: u64, order: Ordering) {
self.lo.store(val, order);
}
#[inline]
pub(crate) fn store_hi(&self, val: u64, order: Ordering) {
self.hi.store(val, order);
}
#[inline]
pub(crate) fn store(&self, lo: u64, hi: u64, order: Ordering) {
self.hi.store(hi, order);
self.lo.store(lo, order);
}
#[inline]
pub(crate) fn exchange_lo(&self, new_lo: u64, order: Ordering) -> u64 {
self.lo.swap(new_lo, order)
}
#[inline]
fn as_u128(&self) -> &AtomicU128 {
unsafe { &*(self as *const Self as *const AtomicU128) }
}
#[inline]
fn pack(lo: u64, hi: u64) -> u128 {
(lo as u128) | ((hi as u128) << 64)
}
#[inline]
fn unpack(v: u128) -> (u64, u64) {
(v as u64, (v >> 64) as u64)
}
#[inline]
pub(crate) fn compare_exchange(
&self,
old_lo: u64,
old_hi: u64,
new_lo: u64,
new_hi: u64,
) -> Result<(u64, u64), (u64, u64)> {
match self.as_u128().compare_exchange(
Self::pack(old_lo, old_hi),
Self::pack(new_lo, new_hi),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(v) => Ok(Self::unpack(v)),
Err(v) => Err(Self::unpack(v)),
}
}
#[inline]
pub(crate) fn compare_exchange_weak(
&self,
old_lo: u64,
old_hi: u64,
new_lo: u64,
new_hi: u64,
) -> Result<(u64, u64), (u64, u64)> {
match self.as_u128().compare_exchange_weak(
Self::pack(old_lo, old_hi),
Self::pack(new_lo, new_hi),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(v) => Ok(Self::unpack(v)),
Err(v) => Err(Self::unpack(v)),
}
}
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x")))]
mod fallback {
use core::sync::atomic::Ordering;
use portable_atomic::AtomicU128;
#[repr(align(16))]
pub(crate) struct WordPair {
data: AtomicU128,
}
impl WordPair {
pub(crate) const fn new(lo: u64, hi: u64) -> Self {
let val = (lo as u128) | ((hi as u128) << 64);
Self {
data: AtomicU128::new(val),
}
}
#[inline]
pub(crate) fn load(&self) -> (u64, u64) {
let val = self.data.load(Ordering::Acquire);
(val as u64, (val >> 64) as u64)
}
#[inline]
pub(crate) fn load_lo(&self) -> u64 {
self.load().0
}
#[inline]
pub(crate) fn load_hi(&self) -> u64 {
self.load().1
}
#[inline]
pub(crate) fn store(&self, lo: u64, hi: u64, order: Ordering) {
let val = (lo as u128) | ((hi as u128) << 64);
self.data.store(val, order);
}
#[inline]
pub(crate) fn store_lo(&self, lo: u64, order: Ordering) {
loop {
let old = self.data.load(Ordering::Acquire);
let hi = (old >> 64) as u64;
let new = (lo as u128) | ((hi as u128) << 64);
if self
.data
.compare_exchange_weak(old, new, order, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
#[inline]
pub(crate) fn store_hi(&self, hi: u64, order: Ordering) {
loop {
let old = self.data.load(Ordering::Acquire);
let lo = old as u64;
let new = (lo as u128) | ((hi as u128) << 64);
if self
.data
.compare_exchange_weak(old, new, order, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
#[inline]
pub(crate) fn compare_exchange(
&self,
old_lo: u64,
old_hi: u64,
new_lo: u64,
new_hi: u64,
) -> Result<(u64, u64), (u64, u64)> {
let old = (old_lo as u128) | ((old_hi as u128) << 64);
let new = (new_lo as u128) | ((new_hi as u128) << 64);
match self
.data
.compare_exchange(old, new, Ordering::AcqRel, Ordering::Acquire)
{
Ok(v) => Ok((v as u64, (v >> 64) as u64)),
Err(v) => Err((v as u64, (v >> 64) as u64)),
}
}
#[inline]
pub(crate) fn compare_exchange_weak(
&self,
old_lo: u64,
old_hi: u64,
new_lo: u64,
new_hi: u64,
) -> Result<(u64, u64), (u64, u64)> {
let old = (old_lo as u128) | ((old_hi as u128) << 64);
let new = (new_lo as u128) | ((new_hi as u128) << 64);
match self
.data
.compare_exchange_weak(old, new, Ordering::AcqRel, Ordering::Acquire)
{
Ok(v) => Ok((v as u64, (v >> 64) as u64)),
Err(v) => Err((v as u64, (v >> 64) as u64)),
}
}
#[inline]
pub(crate) fn exchange_lo(&self, new_lo: u64, order: Ordering) -> u64 {
loop {
let old = self.data.load(Ordering::Acquire);
let old_lo = old as u64;
let hi = (old >> 64) as u64;
let new = (new_lo as u128) | ((hi as u128) << 64);
if self
.data
.compare_exchange_weak(old, new, order, Ordering::Relaxed)
.is_ok()
{
return old_lo;
}
}
}
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x"))]
pub(crate) use native::WordPair;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x")))]
pub(crate) use fallback::WordPair;
pub(crate) const HR_NUM: usize = 1;
pub(crate) const SLOTS_PER_THREAD: usize = HR_NUM + 2;
const SLOTS_PER_PAGE: usize = 128;
const PAGE_SHIFT: usize = 7;
const PAGE_MASK: usize = 127;
const MAX_PAGES: usize = 512;
struct SlotPage([ThreadSlots; SLOTS_PER_PAGE]);
pub(crate) const RETIRE_FREQ: usize = 64;
pub(crate) const EPOCH_FREQ: usize = 128;
pub(crate) struct HelpState {
pub(crate) result: WordPair,
pub(crate) epoch: AtomicU64,
pub(crate) pointer: AtomicU64,
pub(crate) parent: AtomicU64,
}
impl HelpState {
const fn new() -> Self {
Self {
result: WordPair::new(0, 0),
epoch: AtomicU64::new(0),
pointer: AtomicU64::new(0),
parent: AtomicU64::new(0),
}
}
}
#[repr(align(128))]
pub(crate) struct ThreadSlots {
pub(crate) first: [WordPair; SLOTS_PER_THREAD],
pub(crate) epoch: [WordPair; SLOTS_PER_THREAD],
pub(crate) state: [HelpState; SLOTS_PER_THREAD],
}
impl ThreadSlots {
fn new() -> Self {
Self {
first: core::array::from_fn(|_| WordPair::new(INVPTR as u64, 0)),
epoch: core::array::from_fn(|_| WordPair::new(0, 0)),
state: core::array::from_fn(|_| HelpState::new()),
}
}
}
pub(crate) struct ASMRState {
pages: [AtomicPtr<SlotPage>; MAX_PAGES],
epoch: AtomicU64,
slow_counter: AtomicU64,
next_tid: AtomicUsize,
free_tids: TTas<alloc::vec::Vec<usize>>,
}
#[allow(clippy::declare_interior_mutable_const)]
const NULL_PAGE: AtomicPtr<SlotPage> = AtomicPtr::new(core::ptr::null_mut());
impl ASMRState {
fn new() -> Self {
Self {
pages: [NULL_PAGE; MAX_PAGES],
epoch: AtomicU64::new(1),
slow_counter: AtomicU64::new(0),
next_tid: AtomicUsize::new(0),
free_tids: TTas::new(alloc::vec::Vec::new()),
}
}
fn ensure_page(&self, page_idx: usize) {
if self.pages[page_idx].load(Ordering::Acquire).is_null() {
let page = Box::into_raw(Box::new(SlotPage(core::array::from_fn(|_| {
ThreadSlots::new()
}))));
if self.pages[page_idx]
.compare_exchange(
core::ptr::null_mut(),
page,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
unsafe {
drop(Box::from_raw(page));
}
}
}
}
#[inline]
pub(crate) fn thread_slots(&self, tid: usize) -> &ThreadSlots {
let page_idx = tid >> PAGE_SHIFT;
let slot_idx = tid & PAGE_MASK;
let page = self.pages[page_idx].load(Ordering::Acquire);
debug_assert!(
!page.is_null(),
"kovan: page {page_idx} not allocated for tid {tid}"
);
unsafe { &(*page).0[slot_idx] }
}
#[inline]
pub(crate) fn get_epoch(&self) -> u64 {
self.epoch.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn advance_epoch(&self) {
self.epoch.fetch_add(1, Ordering::AcqRel);
}
#[inline]
pub(crate) fn slow_counter(&self) -> u64 {
self.slow_counter.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn inc_slow(&self) {
self.slow_counter.fetch_add(1, Ordering::AcqRel);
}
#[inline]
pub(crate) fn dec_slow(&self) {
self.slow_counter.fetch_sub(1, Ordering::AcqRel);
}
#[inline]
pub(crate) fn max_threads(&self) -> usize {
self.next_tid.load(Ordering::Acquire)
}
pub(crate) fn alloc_tid(&self) -> usize {
{
let mut free = self.free_tids.lock();
if let Some(tid) = free.pop() {
return tid;
}
}
loop {
let current = self.next_tid.load(Ordering::Relaxed);
let page_idx = current >> PAGE_SHIFT;
assert!(
page_idx < MAX_PAGES,
"kovan: exceeded maximum thread count ({})",
MAX_PAGES * SLOTS_PER_PAGE,
);
self.ensure_page(page_idx);
match self.next_tid.compare_exchange_weak(
current,
current + 1,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => return current,
Err(_) => continue,
}
}
}
pub(crate) fn free_tid(&self, tid: usize) {
let slots = self.thread_slots(tid);
for j in 0..SLOTS_PER_THREAD {
slots.first[j].store(INVPTR as u64, 0, Ordering::Release);
slots.epoch[j].store(0, 0, Ordering::Release);
}
let mut free = self.free_tids.lock();
free.push(tid);
}
#[inline]
pub(crate) fn hr_num(&self) -> usize {
HR_NUM
}
}
use once_cell::race::OnceBox;
static GLOBAL: OnceBox<ASMRState> = OnceBox::new();
#[inline]
pub(crate) fn global() -> &'static ASMRState {
GLOBAL.get_or_init(|| Box::new(ASMRState::new()))
}