use core::{
cell::{RefCell, UnsafeCell},
fmt,
mem::MaybeUninit,
ptr::NonNull,
sync::atomic::{AtomicU8, AtomicU64, Ordering},
};
use super::entry::{FastReleaseAttempt, try_release_current_owner_word};
use crate::thread::{ParkTicket, ThreadHandle};
static NEXT_PI_MUTEX_GENERATION: AtomicU64 = AtomicU64::new(1);
const OWNER_HAS_WAITERS: u64 = 1 << 63;
const OWNER_ID_MASK: u64 = !OWNER_HAS_WAITERS;
const WAIT_STORAGE_UNINITIALIZED: u8 = 0;
const WAIT_STORAGE_INITIALIZING: u8 = 1;
const WAIT_STORAGE_READY: u8 = 2;
#[doc(hidden)]
pub const PI_MUTEX_WAIT_STORAGE_WORDS: usize = 5;
pub struct PiMutexWaitStorage {
state: AtomicU8,
words: UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>,
}
impl PiMutexWaitStorage {
const fn new() -> Self {
Self {
state: AtomicU8::new(WAIT_STORAGE_UNINITIALIZED),
words: UnsafeCell::new([MaybeUninit::uninit(); PI_MUTEX_WAIT_STORAGE_WORDS]),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PiMutexWaitStorageView<'lock> {
state: &'lock AtomicU8,
words: &'lock UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>,
}
impl<'lock> PiMutexWaitStorageView<'lock> {
#[doc(hidden)]
const fn from_parts(
state: &'lock AtomicU8,
words: &'lock UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>,
) -> Self {
Self { state, words }
}
#[doc(hidden)]
pub const fn as_ptr(self) -> *mut () {
self.words.get().cast()
}
#[doc(hidden)]
pub fn is_initialized(self) -> bool {
self.state.load(Ordering::Acquire) == WAIT_STORAGE_READY
}
#[doc(hidden)]
pub unsafe fn get_or_init<T>(self, init: impl FnOnce() -> T) -> &'lock T {
assert!(
core::mem::size_of::<T>()
<= PI_MUTEX_WAIT_STORAGE_WORDS * core::mem::size_of::<usize>(),
"PI mutex provider waiter state exceeds inline storage"
);
assert!(
core::mem::align_of::<T>() <= core::mem::align_of::<usize>(),
"PI mutex provider waiter state exceeds inline alignment"
);
if self
.state
.compare_exchange(
WAIT_STORAGE_UNINITIALIZED,
WAIT_STORAGE_INITIALIZING,
Ordering::Acquire,
Ordering::Acquire,
)
.is_ok()
{
unsafe { self.as_ptr().cast::<T>().write(init()) };
self.state.store(WAIT_STORAGE_READY, Ordering::Release);
} else {
while self.state.load(Ordering::Acquire) == WAIT_STORAGE_INITIALIZING {
core::hint::spin_loop();
}
assert_eq!(
self.state.load(Ordering::Acquire),
WAIT_STORAGE_READY,
"PI mutex waiter storage has an invalid lifecycle"
);
}
unsafe { &*self.as_ptr().cast::<T>() }
}
#[doc(hidden)]
pub unsafe fn get<T>(self) -> Option<&'lock T> {
if self.state.load(Ordering::Acquire) != WAIT_STORAGE_READY {
return None;
}
Some(unsafe { &*self.as_ptr().cast::<T>() })
}
}
fn take_initialized_wait_storage(
state: &mut u8,
words: &mut [MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS],
) -> Option<*mut ()> {
match *state {
WAIT_STORAGE_UNINITIALIZED => None,
WAIT_STORAGE_READY => {
*state = WAIT_STORAGE_UNINITIALIZED;
Some(words.as_mut_ptr().cast())
}
_ => panic!("destroying PI mutex while waiter storage initializes"),
}
}
unsafe impl Sync for PiMutexWaitStorage {}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PiTaskId(u64);
impl PiTaskId {
pub const fn new(raw: u64) -> Option<Self> {
if raw == 0 || raw & OWNER_HAS_WAITERS != 0 {
None
} else {
Some(Self(raw))
}
}
pub const fn get(self) -> u64 {
self.0
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PiMutexId(u64);
impl PiMutexId {
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PiMutexStateError {
WaiterOwnsLock,
InvalidState,
}
impl fmt::Display for PiMutexStateError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::WaiterOwnsLock => "PI mutex waiter already owns the lock",
Self::InvalidState => "invalid PI mutex state",
})
}
}
impl core::error::Error for PiMutexStateError {}
pub struct PiMutexCore {
owner: AtomicU64,
generation: AtomicU64,
wait_storage: PiMutexWaitStorage,
}
#[derive(Clone, Copy, Debug)]
pub struct PiMutexCoreView<'lock> {
owner: &'lock AtomicU64,
generation: &'lock AtomicU64,
wait_storage: PiMutexWaitStorageView<'lock>,
}
impl<'lock> PiMutexCoreView<'lock> {
#[doc(hidden)]
pub(in crate::sync) const fn from_parts(
owner: &'lock AtomicU64,
generation: &'lock AtomicU64,
wait_state: &'lock AtomicU8,
wait_words: &'lock UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>,
) -> Self {
Self {
owner,
generation,
wait_storage: PiMutexWaitStorageView::from_parts(wait_state, wait_words),
}
}
pub fn try_acquire(self, current: PiTaskId) -> Result<PiMutexAcquire, PiMutexStateError> {
match self
.owner
.compare_exchange(0, current.get(), Ordering::Acquire, Ordering::Relaxed)
{
Ok(_) => Ok(PiMutexAcquire::Acquired),
Err(owner) if owner & OWNER_ID_MASK == current.get() => {
Err(PiMutexStateError::WaiterOwnsLock)
}
Err(_) => Ok(PiMutexAcquire::Contended),
}
}
#[doc(hidden)]
pub unsafe fn try_acquire_for_thread<T>(
self,
current: T,
) -> Result<PiMutexAcquire, PiMutexStateError>
where
T: Into<PiTaskId>,
{
self.try_acquire(current.into())
}
#[doc(hidden)]
pub unsafe fn try_release_for_thread<T>(self, current: T) -> Result<bool, PiMutexStateError>
where
T: Into<PiTaskId>,
{
let current = current.into();
match try_release_current_owner_word(self.owner, current.get(), OWNER_ID_MASK) {
FastReleaseAttempt::Released => Ok(true),
FastReleaseAttempt::Contended => Ok(false),
FastReleaseAttempt::InvalidOwner => Err(PiMutexStateError::InvalidState),
}
}
pub unsafe fn try_release_owned(
self,
current: PiTaskId,
) -> Result<PiMutexOwnedRelease, PiMutexStateError> {
match try_release_current_owner_word(self.owner, current.get(), OWNER_ID_MASK) {
FastReleaseAttempt::Released => Ok(PiMutexOwnedRelease::Released),
FastReleaseAttempt::Contended => Ok(PiMutexOwnedRelease::Contended(current)),
FastReleaseAttempt::InvalidOwner => Err(PiMutexStateError::InvalidState),
}
}
pub fn is_owned_by(self, current: PiTaskId) -> bool {
owner_from_word(self.owner.load(Ordering::Acquire)) == Some(current)
}
pub fn is_locked(self) -> bool {
self.owner.load(Ordering::Relaxed) != 0
}
pub fn mutex_ref(self) -> Result<PiMutexRef<'lock>, PiMutexStateError> {
let observed = self.generation.load(Ordering::Acquire);
if observed != 0 {
return Ok(PiMutexRef {
core: self,
id: PiMutexId(observed),
});
}
let allocated = NEXT_PI_MUTEX_GENERATION
.try_update(Ordering::AcqRel, Ordering::Acquire, |next| {
next.checked_add(1)
})
.map(PiMutexId)
.map_err(|_| PiMutexStateError::InvalidState)?;
match self
.generation
.compare_exchange(0, allocated.0, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => Ok(PiMutexRef {
core: self,
id: allocated,
}),
Err(installed) if installed != 0 => Ok(PiMutexRef {
core: self,
id: PiMutexId(installed),
}),
Err(_) => Err(PiMutexStateError::InvalidState),
}
}
#[doc(hidden)]
pub fn owner_snapshot(self) -> PiMutexOwnerSnapshot {
let word = self.owner.load(Ordering::Acquire);
PiMutexOwnerSnapshot {
word,
owner: owner_from_word(word),
}
}
#[doc(hidden)]
pub fn try_acquire_snapshot(self, snapshot: PiMutexOwnerSnapshot, current: PiTaskId) -> bool {
debug_assert_eq!(snapshot.word, 0);
self.owner
.compare_exchange(
snapshot.word,
current.get(),
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
}
#[doc(hidden)]
pub fn try_mark_waiters(self, snapshot: PiMutexOwnerSnapshot) -> bool {
if snapshot.has_waiters() {
return self.owner.load(Ordering::Acquire) == snapshot.word;
}
self.owner
.compare_exchange(
snapshot.word,
snapshot.word | OWNER_HAS_WAITERS,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
#[doc(hidden)]
pub fn publish_owner(self, owner: PiTaskId, has_waiters: bool) {
self.owner.store(
owner.get() | if has_waiters { OWNER_HAS_WAITERS } else { 0 },
Ordering::Release,
);
}
#[doc(hidden)]
pub fn publish_ownerless(self) {
self.owner.store(OWNER_HAS_WAITERS, Ordering::Release);
}
#[doc(hidden)]
pub fn publish_unlocked(self) {
self.owner.store(0, Ordering::Release);
}
#[doc(hidden)]
pub fn clear_waiters_bit(self, owner: PiTaskId) {
self.owner.store(owner.get(), Ordering::Release);
}
#[doc(hidden)]
pub const fn wait_storage(self) -> PiMutexWaitStorageView<'lock> {
self.wait_storage
}
}
impl PiMutexCore {
pub const fn new() -> Self {
Self {
owner: AtomicU64::new(0),
generation: AtomicU64::new(0),
wait_storage: PiMutexWaitStorage::new(),
}
}
#[doc(hidden)]
pub const fn view(&self) -> PiMutexCoreView<'_> {
PiMutexCoreView::from_parts(
&self.owner,
&self.generation,
&self.wait_storage.state,
&self.wait_storage.words,
)
}
}
impl fmt::Debug for PiMutexCore {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PiMutexCore")
.field(
"owner",
&owner_from_word(self.owner.load(Ordering::Relaxed)),
)
.field("generation", &self.generation.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl Default for PiMutexCore {
fn default() -> Self {
Self::new()
}
}
impl Drop for PiMutexCore {
fn drop(&mut self) {
destroy_pi_mutex_storage(
&mut self.owner,
&mut self.generation,
&mut self.wait_storage.state,
&mut self.wait_storage.words,
);
}
}
pub(in crate::sync) fn destroy_pi_mutex_storage(
owner: &mut AtomicU64,
generation: &mut AtomicU64,
wait_state: &mut AtomicU8,
wait_words: &mut UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>,
) {
*owner.get_mut() = 0;
*generation.get_mut() = 0;
if let Some(wait_handle) =
take_initialized_wait_storage(wait_state.get_mut(), wait_words.get_mut())
{
unsafe { crate::thread::drop_pi_mutex_wait_handle(wait_handle) };
}
}
#[derive(Clone, Copy, Debug)]
pub struct PiMutexRef<'lock> {
core: PiMutexCoreView<'lock>,
id: PiMutexId,
}
impl<'lock> PiMutexRef<'lock> {
pub const fn id(self) -> PiMutexId {
self.id
}
#[doc(hidden)]
pub const fn core(self) -> PiMutexCoreView<'lock> {
self.core
}
#[doc(hidden)]
pub fn raw(self) -> PiMutexRaw {
PiMutexRaw {
owner: NonNull::from(self.core.owner),
generation: NonNull::from(self.core.generation),
wait_state: NonNull::from(self.core.wait_storage.state),
wait_words: NonNull::from(self.core.wait_storage.words),
id: self.id,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PiMutexRaw {
owner: NonNull<AtomicU64>,
generation: NonNull<AtomicU64>,
wait_state: NonNull<AtomicU8>,
wait_words: NonNull<UnsafeCell<[MaybeUninit<usize>; PI_MUTEX_WAIT_STORAGE_WORDS]>>,
id: PiMutexId,
}
impl PiMutexRaw {
pub const fn id(self) -> PiMutexId {
self.id
}
#[doc(hidden)]
pub unsafe fn core(self) -> PiMutexCoreView<'static> {
PiMutexCoreView {
owner: unsafe { self.owner.as_ref() },
generation: unsafe { self.generation.as_ref() },
wait_storage: PiMutexWaitStorageView {
state: unsafe { self.wait_state.as_ref() },
words: unsafe { self.wait_words.as_ref() },
},
}
}
}
unsafe impl Send for PiMutexRaw {}
unsafe impl Sync for PiMutexRaw {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PiMutexOwnerSnapshot {
word: u64,
owner: Option<PiTaskId>,
}
impl PiMutexOwnerSnapshot {
pub const fn owner(self) -> Option<PiTaskId> {
self.owner
}
pub const fn is_unlocked(self) -> bool {
self.word == 0
}
pub const fn is_ownerless(self) -> bool {
self.word == OWNER_HAS_WAITERS
}
pub const fn has_waiters(self) -> bool {
self.word & OWNER_HAS_WAITERS != 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PiMutexAcquire {
Acquired,
Contended,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PiMutexOwnedRelease {
Released,
Contended(PiTaskId),
}
#[must_use = "a PI wait token must be granted or explicitly cancelled"]
#[derive(Debug)]
pub struct PiWaitToken {
thread: PiTaskId,
initial_owner: Option<ThreadHandle>,
generation: u64,
lock: PiMutexRaw,
provider_waiter: NonNull<()>,
prepared_park: RefCell<Option<ParkTicket>>,
}
impl PiWaitToken {
#[doc(hidden)]
pub unsafe fn from_registration(
lock: PiMutexRaw,
thread: PiTaskId,
initial_owner: Option<ThreadHandle>,
generation: u64,
provider_waiter: NonNull<()>,
) -> Self {
Self {
thread,
initial_owner,
generation,
lock,
provider_waiter,
prepared_park: RefCell::new(None),
}
}
pub(crate) fn install_prepared_park(&self, ticket: ParkTicket) {
assert_eq!(
ticket.thread().as_u64(),
self.thread.get(),
"PI waiter park ticket must belong to the registered task"
);
assert!(
self.prepared_park.replace(Some(ticket)).is_none(),
"PI waiter may own only one prepared park"
);
}
pub(crate) fn take_prepared_park(&self) -> Option<ParkTicket> {
self.prepared_park.take()
}
pub const fn thread_id(&self) -> PiTaskId {
self.thread
}
pub fn initial_owner(&self) -> Option<PiTaskId> {
self.initial_owner
.as_ref()
.map(|owner| PiTaskId::from(owner.id()))
}
#[doc(hidden)]
pub(crate) fn initial_owner_handle(&self) -> Option<&ThreadHandle> {
self.initial_owner.as_ref()
}
#[doc(hidden)]
pub const fn generation(&self) -> u64 {
self.generation
}
#[doc(hidden)]
pub const fn lock_raw(&self) -> PiMutexRaw {
self.lock
}
#[doc(hidden)]
pub const unsafe fn provider_waiter(&self) -> NonNull<()> {
self.provider_waiter
}
pub fn is_granted(&self) -> bool {
crate::runtime::sync::pi_waiter_is_granted(self)
}
pub fn can_claim(&self) -> bool {
self.is_top_waiter() && unsafe { self.lock.core() }.owner_snapshot().is_ownerless()
}
pub fn is_top_waiter(&self) -> bool {
crate::runtime::sync::pi_waiter_is_top(self)
}
pub fn initial_owner_is_on_cpu(&self) -> bool {
super::task_result(
crate::runtime::sync::pi_initial_owner_is_on_cpu(self),
"observe PI mutex owner execution state",
)
}
}
#[must_use = "a registered PI waiter must be blocked, claimed, or cancelled"]
#[derive(Debug)]
pub enum PiMutexLockResult {
Acquired,
Waiting(PiWaitToken),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PiMutexClaimOutcome {
Claimed,
Retry,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PiWaitCancelOutcome {
Cancelled,
HandoffPending,
}
fn owner_from_word(state: u64) -> Option<PiTaskId> {
PiTaskId::new(state & OWNER_ID_MASK)
}
#[cfg(test)]
mod tests;