use core::{marker::PhantomData, ptr::NonNull};
use cpu_local::{CpuPin, ExclusiveCpu};
use crate::PerCpuArea;
#[doc(hidden)]
pub unsafe trait PerCpuSymbol<T> {
fn offset() -> usize;
fn current_ptr(pin: &CpuPin<'_>) -> NonNull<T>;
fn remote_ptr(area: PerCpuArea) -> NonNull<T>;
}
#[doc(hidden)]
pub unsafe trait PerCpuObjectSymbol<T>: PerCpuSymbol<T> {}
#[doc(hidden)]
pub unsafe trait PerCpuPrimitiveSymbol<T>: PerCpuSymbol<T> {}
type PerCpuMarker<T, S> = fn() -> (T, S);
pub struct PerCpu<T, S> {
_marker: PhantomData<PerCpuMarker<T, S>>,
}
impl<T, S> PerCpu<T, S>
where
S: PerCpuSymbol<T>,
{
#[doc(hidden)]
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
pub fn offset(&self) -> usize {
S::offset()
}
pub fn current_ptr(&self, pin: &CpuPin<'_>) -> NonNull<T> {
S::current_ptr(pin)
}
pub fn remote_ptr(&self, area: PerCpuArea) -> NonNull<T> {
S::remote_ptr(area)
}
}
impl<T, S> PerCpu<T, S>
where
S: PerCpuObjectSymbol<T>,
{
pub fn with_current_mut<R>(
&self,
exclusive: &ExclusiveCpu<'_>,
operation: impl for<'value> FnOnce(&'value mut T) -> R,
) -> R {
let mut pointer =
unsafe { NonNull::new_unchecked((exclusive.area().base() + S::offset()) as *mut T) };
operation(unsafe { pointer.as_mut() })
}
#[doc(hidden)]
pub unsafe fn with_current_cpu_area_mut<R>(
&self,
operation: impl for<'value> FnOnce(&'value mut T) -> R,
) -> Result<R, cpu_local::CpuLocalError> {
unsafe {
cpu_local::with_current_cpu_area(|area| {
let mut pointer = area.symbol_ptr::<T>(S::offset())?;
Ok(operation(pointer.as_mut()))
})?
}
}
}
impl<T, S> PerCpu<T, S>
where
T: Sync,
S: PerCpuObjectSymbol<T>,
{
pub fn with_current<R>(
&self,
pin: &CpuPin<'_>,
operation: impl for<'value> FnOnce(&'value T) -> R,
) -> R {
operation(unsafe { S::current_ptr(pin).as_ref() })
}
#[doc(hidden)]
pub unsafe fn with_current_cpu_area<R>(
&self,
operation: impl for<'value> FnOnce(&'value T) -> R,
) -> Result<R, cpu_local::CpuLocalError> {
unsafe {
cpu_local::with_current_cpu_area(|area| {
let pointer = area.symbol_ptr::<T>(S::offset())?;
Ok(operation(pointer.as_ref()))
})?
}
}
}
mod primitive {
use core::{
ptr::NonNull,
sync::atomic::{
AtomicBool, AtomicU8, AtomicU16, AtomicU32, AtomicU64, AtomicUsize, Ordering,
},
};
pub trait Sealed: Copy {
unsafe fn load(pointer: NonNull<Self>) -> Self;
unsafe fn store(pointer: NonNull<Self>, value: Self);
}
macro_rules! impl_atomic_primitive {
($value:ty, $atomic:ty) => {
impl Sealed for $value {
unsafe fn load(pointer: NonNull<Self>) -> Self {
unsafe { pointer.cast::<$atomic>().as_ref() }.load(Ordering::Relaxed)
}
unsafe fn store(pointer: NonNull<Self>, value: Self) {
unsafe { pointer.cast::<$atomic>().as_ref() }.store(value, Ordering::Relaxed);
}
}
};
}
impl_atomic_primitive!(bool, AtomicBool);
impl_atomic_primitive!(u8, AtomicU8);
impl_atomic_primitive!(u16, AtomicU16);
impl_atomic_primitive!(u32, AtomicU32);
impl_atomic_primitive!(u64, AtomicU64);
impl_atomic_primitive!(usize, AtomicUsize);
}
impl<T, S> PerCpu<T, S>
where
T: primitive::Sealed,
S: PerCpuPrimitiveSymbol<T>,
{
pub fn read_current(&self, pin: &CpuPin<'_>) -> T {
unsafe { T::load(S::current_ptr(pin)) }
}
pub fn write_current(&self, pin: &CpuPin<'_>, value: T) {
unsafe { T::store(S::current_ptr(pin), value) }
}
}