use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::cell::{Cell, OnceCell};
use core::num::NonZero;
use core::ops::Deref;
use core::ptr;
use alloc::sync::Arc;
use parking_lot::Mutex;
use eko::thread::ThreadLocal;
use crate::rustc_data_structures::outline;
use crate::rustc_data_structures::sync::CacheAligned;
#[derive(Clone, Copy, PartialEq)]
struct RegistryId(*const RegistryData);
impl RegistryId {
#[inline(always)]
fn verify(self) -> usize {
let (id, index) = THREAD_DATA
.with(ThreadData::new, |data| (data.registry_id.get(), data.index.get()))
.expect("out of thread-local slots");
if id == self { index } else { outline(|| panic!("Unable to verify registry association")) }
}
}
struct RegistryData {
thread_limit: NonZero<usize>,
threads: Mutex<usize>,
}
#[derive(Clone)]
pub struct Registry(Arc<RegistryData>);
static REGISTRY: ThreadLocal<OnceCell<Registry>> = ThreadLocal::new();
struct ThreadData {
registry_id: Cell<RegistryId>,
index: Cell<usize>,
}
impl ThreadData {
fn new() -> ThreadData {
ThreadData { registry_id: Cell::new(RegistryId(ptr::null())), index: Cell::new(0) }
}
}
static THREAD_DATA: ThreadLocal<ThreadData> = ThreadLocal::new();
impl Registry {
pub fn new(thread_limit: NonZero<usize>) -> Self {
Registry(Arc::new(RegistryData { thread_limit, threads: Mutex::new(0) }))
}
pub fn current() -> Self {
REGISTRY
.with(OnceCell::new, |registry| registry.get().cloned())
.expect("out of thread-local slots")
.expect("No associated registry")
}
pub fn try_current() -> Option<Self> {
REGISTRY.with(OnceCell::new, |registry| registry.get().cloned()).flatten()
}
pub fn register(&self) {
let mut threads = self.0.threads.lock();
if *threads < self.0.thread_limit.get() {
REGISTRY
.with(OnceCell::new, |registry| {
if registry.get().is_some() {
drop(threads);
panic!("Thread already has a registry");
}
registry.set(self.clone()).ok();
THREAD_DATA
.with(ThreadData::new, |data| {
data.registry_id.set(self.id());
data.index.set(*threads);
})
.expect("out of thread-local slots");
*threads += 1;
})
.expect("out of thread-local slots");
} else {
drop(threads);
panic!("Thread limit reached");
}
}
fn id(&self) -> RegistryId {
RegistryId(&*self.0)
}
}
pub struct WorkerLocal<T> {
locals: Box<[CacheAligned<T>]>,
registry: Registry,
}
unsafe impl<T: Send> Sync for WorkerLocal<T> {}
impl<T> WorkerLocal<T> {
#[inline]
pub fn new<F: FnMut(usize) -> T>(mut initial: F) -> WorkerLocal<T> {
let registry = Registry::current();
WorkerLocal {
locals: (0..registry.0.thread_limit.get()).map(|i| CacheAligned(initial(i))).collect(),
registry,
}
}
#[inline]
pub fn into_inner(self) -> impl Iterator<Item = T> {
self.locals.into_vec().into_iter().map(|local| local.0)
}
}
impl<T> Deref for WorkerLocal<T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
unsafe { &self.locals.get_unchecked(self.registry.id().verify()).0 }
}
}
impl<T: Default> Default for WorkerLocal<T> {
fn default() -> Self {
WorkerLocal::new(|_| T::default())
}
}