use std::any::Any;
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::{Rc, Weak};
use std::sync::{LockResult, OnceLock, PoisonError, TryLockError, TryLockResult};
#[doc(hidden)]
pub use inventory;
pub struct MutatorSlot {
pub name: &'static str,
pub init: fn() -> Box<dyn Any>,
pub swap: fn(&mut dyn Any),
pub trace: fn(&dyn Any, &mut crate::MarkVisitor),
pub check: fn(),
}
inventory::collect!(MutatorSlot);
pub fn slots() -> &'static [&'static MutatorSlot] {
static SLOTS: OnceLock<Vec<&'static MutatorSlot>> = OnceLock::new();
SLOTS.get_or_init(|| {
let mut slots: Vec<&'static MutatorSlot> = inventory::iter::<MutatorSlot>().collect();
slots.sort_by_key(|s| s.name);
for pair in slots.windows(2) {
assert!(
pair[0].name != pair[1].name,
"duplicate mutator slot `{}`",
pair[0].name
);
}
slots
})
}
pub fn slot_names() -> Vec<&'static str> {
slots().iter().map(|s| s.name).collect()
}
#[macro_export]
macro_rules! mutator_slot {
($name:literal, refcell $tl:ident : $ty:ty $(, trace $tracer:path)?) => {
const _: () = {
fn check() {
$tl.with(|cell| {
let _borrow = cell.try_borrow_mut().expect(concat!(
"mutator slot ", $name, " is borrowed across a context switch"
));
});
}
fn init() -> ::std::boxed::Box<dyn ::std::any::Any> {
::std::boxed::Box::new(<$ty as ::std::default::Default>::default())
}
fn swap(value: &mut dyn ::std::any::Any) {
let value = value.downcast_mut::<$ty>().expect(concat!(
"mutator slot ",
$name,
": type mismatch"
));
$tl.with(|cell| {
let mut current = cell.try_borrow_mut().expect(concat!(
"mutator slot ",
$name,
" is borrowed across a context switch"
));
::std::mem::swap(&mut *current, value);
});
}
fn trace(value: &dyn ::std::any::Any, visitor: &mut $crate::MarkVisitor) {
let _ = (value, &visitor);
$( $tracer(value.downcast_ref::<$ty>().expect("mutator trace type mismatch"), visitor); )?
}
$crate::mutator::inventory::submit! {
$crate::mutator::MutatorSlot { name: $name, init, swap, trace, check }
}
};
};
($name:literal, cell $tl:ident : $ty:ty $(, trace $tracer:path)?) => {
const _: () = {
fn check() {}
fn init() -> ::std::boxed::Box<dyn ::std::any::Any> {
::std::boxed::Box::new(<$ty as ::std::default::Default>::default())
}
fn swap(value: &mut dyn ::std::any::Any) {
let value = value.downcast_mut::<$ty>().expect(concat!(
"mutator slot ",
$name,
": type mismatch"
));
$tl.with(|cell| {
let incoming = ::std::mem::take(value);
*value = cell.replace(incoming);
});
}
fn trace(value: &dyn ::std::any::Any, visitor: &mut $crate::MarkVisitor) {
let _ = (value, &visitor);
$( $tracer(value.downcast_ref::<$ty>().expect("mutator trace type mismatch"), visitor); )?
}
$crate::mutator::inventory::submit! {
$crate::mutator::MutatorSlot { name: $name, init, swap, trace, check }
}
};
};
}
pub struct MutatorState {
values: Rc<RefCell<SlotValues>>,
_not_send: PhantomData<*const ()>,
}
type SlotValues = Box<[Option<Box<dyn Any>>]>;
thread_local! {
static DETACHED: RefCell<Vec<Weak<RefCell<SlotValues>>>> = const { RefCell::new(Vec::new()) };
}
#[cfg(not(feature = "no-gc"))]
pub(crate) fn trace_suspended(visitor: &mut crate::MarkVisitor) {
DETACHED.with(|states| {
states.borrow_mut().retain(|weak| {
let Some(values) = weak.upgrade() else {
return false;
};
for (slot, value) in slots().iter().zip(values.borrow().iter()) {
if let Some(value) = value {
(slot.trace)(&**value, visitor);
}
}
true
});
});
}
impl MutatorState {
pub fn fresh() -> Self {
let values = slots().iter().map(|_| None).collect();
Self {
values: {
let values = Rc::new(RefCell::new(values));
DETACHED.with(|states| {
let mut states = states.borrow_mut();
states.retain(|w| w.strong_count() > 0);
states.push(Rc::downgrade(&values));
});
values
},
_not_send: PhantomData,
}
}
pub fn save() -> Self {
let mut state = Self::fresh();
state.swap_with_current();
state
}
pub fn restore(mut self) {
self.swap_with_current();
}
pub fn swap_with_current(&mut self) {
check_slots();
for (slot, value) in slots().iter().zip(self.values.borrow_mut().iter_mut()) {
let value = value.get_or_insert_with(slot.init);
(slot.swap)(&mut **value);
}
}
}
impl Default for MutatorState {
fn default() -> Self {
Self::fresh()
}
}
thread_local! {
static GUARDS_HELD: Cell<usize> = const { Cell::new(0) };
}
pub fn guards_held() -> usize {
GUARDS_HELD.with(Cell::get)
}
#[track_caller]
pub fn assert_parkable() {
check_slots();
if cfg!(debug_assertions) {
let held = guards_held();
assert!(
held == 0,
"fiber parked while holding {held} runtime lock guard(s)"
);
assert!(
!crate::governor::in_critical_section(),
"fiber parked inside a governor critical section"
);
}
}
fn check_slots() {
for slot in slots() {
(slot.check)();
}
}
#[derive(Default)]
pub struct TrackedMutex<T: ?Sized> {
inner: std::sync::Mutex<T>,
}
impl<T> TrackedMutex<T> {
pub const fn new(value: T) -> Self {
Self {
inner: std::sync::Mutex::new(value),
}
}
pub fn into_inner(self) -> LockResult<T> {
self.inner.into_inner()
}
}
impl<T: ?Sized> TrackedMutex<T> {
pub fn lock(&self) -> LockResult<TrackedMutexGuard<'_, T>> {
match self.inner.lock() {
Ok(guard) => Ok(TrackedMutexGuard::new(guard)),
Err(poison) => Err(PoisonError::new(TrackedMutexGuard::new(
poison.into_inner(),
))),
}
}
pub fn try_lock(&self) -> TryLockResult<TrackedMutexGuard<'_, T>> {
match self.inner.try_lock() {
Ok(guard) => Ok(TrackedMutexGuard::new(guard)),
Err(TryLockError::Poisoned(poison)) => Err(TryLockError::Poisoned(PoisonError::new(
TrackedMutexGuard::new(poison.into_inner()),
))),
Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
}
}
pub fn get_mut(&mut self) -> LockResult<&mut T> {
self.inner.get_mut()
}
pub fn is_poisoned(&self) -> bool {
self.inner.is_poisoned()
}
}
impl<T: ?Sized + std::fmt::Debug> std::fmt::Debug for TrackedMutex<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
pub struct TrackedMutexGuard<'a, T: ?Sized> {
guard: std::sync::MutexGuard<'a, T>,
}
impl<'a, T: ?Sized> TrackedMutexGuard<'a, T> {
fn new(guard: std::sync::MutexGuard<'a, T>) -> Self {
if cfg!(debug_assertions) {
GUARDS_HELD.with(|n| n.set(n.get() + 1));
}
Self { guard }
}
}
impl<T: ?Sized> Drop for TrackedMutexGuard<'_, T> {
fn drop(&mut self) {
if cfg!(debug_assertions) {
let _ = GUARDS_HELD.try_with(|n| n.set(n.get().saturating_sub(1)));
}
}
}
impl<T: ?Sized> Deref for TrackedMutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.guard
}
}
impl<T: ?Sized> DerefMut for TrackedMutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.guard
}
}
impl<T: ?Sized + std::fmt::Debug> std::fmt::Debug for TrackedMutexGuard<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.guard.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
thread_local! {
static TEST_STACK: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
static TEST_COUNTER: Cell<u64> = const { Cell::new(0) };
}
crate::mutator_slot!("cljrs-gc/TEST_STACK", refcell TEST_STACK: Vec<u32>);
crate::mutator_slot!("cljrs-gc/TEST_COUNTER", cell TEST_COUNTER: u64);
fn stack() -> Vec<u32> {
TEST_STACK.with(|s| s.borrow().clone())
}
#[test]
fn slots_are_registered_and_sorted() {
let names = slot_names();
assert!(names.contains(&"cljrs-gc/TEST_STACK"));
assert!(names.contains(&"cljrs-gc/TEST_COUNTER"));
assert!(names.windows(2).all(|w| w[0] < w[1]));
}
#[test]
fn save_restore_round_trips() {
TEST_STACK.with(|s| s.borrow_mut().extend([1, 2, 3]));
TEST_COUNTER.with(|c| c.set(7));
let saved = MutatorState::save();
assert!(stack().is_empty());
assert_eq!(TEST_COUNTER.with(Cell::get), 0);
TEST_STACK.with(|s| s.borrow_mut().push(99));
let mut other = MutatorState::save();
saved.restore();
assert_eq!(stack(), vec![1, 2, 3]);
assert_eq!(TEST_COUNTER.with(Cell::get), 7);
other.swap_with_current();
assert_eq!(stack(), vec![99]);
other.swap_with_current();
assert_eq!(stack(), vec![1, 2, 3]);
TEST_STACK.with(|s| s.borrow_mut().clear());
TEST_COUNTER.with(|c| c.set(0));
}
#[test]
#[should_panic(expected = "borrowed across a context switch")]
fn swapping_a_borrowed_slot_panics() {
TEST_STACK.with(|s| {
let _held = s.borrow();
let mut state = MutatorState::fresh();
state.swap_with_current();
});
}
#[test]
fn tracked_mutex_counts_guards() {
let m = TrackedMutex::new(5);
let before = guards_held();
{
let g = m.lock().unwrap();
assert_eq!(*g, 5);
if cfg!(debug_assertions) {
assert_eq!(guards_held(), before + 1);
}
}
assert_eq!(guards_held(), before);
assert_parkable();
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "runtime lock guard")]
fn parking_with_a_guard_held_panics() {
let m = TrackedMutex::new(());
let _g = m.lock().unwrap();
assert_parkable();
}
}