use std::{
borrow::BorrowMut,
cell::{RefCell, UnsafeCell},
mem::MaybeUninit,
ptr::NonNull,
};
#[cfg(feature = "experimental-async")]
use crate::LocalRwLockWriteGuard;
use super::{AsStoreMut, AsStoreRef, StoreInner, StoreMut, StoreRef};
use wasmer_types::StoreId;
enum StoreContextEntry {
Sync(*mut StoreInner),
#[cfg(feature = "experimental-async")]
Async(LocalRwLockWriteGuard<Box<StoreInner>>),
}
impl StoreContextEntry {
fn as_ptr(&self) -> *mut StoreInner {
match self {
Self::Sync(ptr) => *ptr,
#[cfg(feature = "experimental-async")]
Self::Async(guard) => &***guard as *const _ as *mut _,
}
}
}
pub(crate) struct StoreContext {
id: StoreId,
borrow_count: u32,
entry: UnsafeCell<StoreContextEntry>,
}
pub(crate) struct StorePtrWrapper {
store_ptr: *mut StoreInner,
}
#[cfg(feature = "experimental-async")]
pub(crate) struct StoreAsyncGuardWrapper {
pub(crate) guard: *mut LocalRwLockWriteGuard<Box<StoreInner>>,
}
pub(crate) struct StorePtrPauseGuard {
store_id: StoreId,
ptr: *mut StoreInner,
ref_count_decremented: bool,
}
#[cfg(feature = "experimental-async")]
pub(crate) enum GetStoreAsyncGuardResult {
Ok(StoreAsyncGuardWrapper),
NotAsync(StorePtrWrapper),
NotInstalled,
}
pub(crate) struct ForcedStoreInstallGuard {
store_id: StoreId,
}
pub(crate) struct StoreInstallGuard {
store_id: Option<StoreId>,
}
thread_local! {
static STORE_CONTEXT_STACK: RefCell<Vec<StoreContext>> = const { RefCell::new(Vec::new()) };
}
impl StoreContext {
fn is_active(id: StoreId) -> bool {
STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
stack.last().is_some_and(|ctx| ctx.id == id)
})
}
fn is_suspended(id: StoreId) -> bool {
!Self::is_active(id)
&& STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
stack.iter().rev().skip(1).any(|ctx| ctx.id == id)
})
}
fn push(id: StoreId, entry: StoreContextEntry) {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
stack.push(Self {
id,
borrow_count: 0,
entry: UnsafeCell::new(entry),
});
})
}
#[cfg(feature = "unsafe-cothread")]
fn push_cothread(id: StoreId, store_ptr: NonNull<StoreInner>) {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
stack.push(Self {
id,
borrow_count: 1,
entry: UnsafeCell::new(StoreContextEntry::Sync(store_ptr.as_ptr())),
});
});
}
#[cfg(feature = "unsafe-cothread")]
fn uninstall_cothread(id: StoreId) {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
if let Some(pos) = stack.iter().rposition(|ctx| ctx.id == id) {
stack.remove(pos);
} else {
panic!(
"CoroutineStoreGuard::drop: entry not found in context stack; \
the store context stack is corrupted"
);
}
});
}
pub(crate) fn is_empty() -> bool {
STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
stack.is_empty()
})
}
#[cfg(feature = "experimental-async")]
pub(crate) fn install_async(
guard: LocalRwLockWriteGuard<Box<StoreInner>>,
) -> ForcedStoreInstallGuard {
let store_id = guard.objects.id();
Self::push(store_id, StoreContextEntry::Async(guard));
ForcedStoreInstallGuard { store_id }
}
fn active_is_async(id: StoreId) -> bool {
STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
let Some(top) = stack.last() else {
return false;
};
if top.id != id {
return false;
}
match unsafe { top.entry.get().as_ref().unwrap() } {
StoreContextEntry::Sync(_) => false,
#[cfg(feature = "experimental-async")]
StoreContextEntry::Async(_) => true,
}
})
}
pub(crate) unsafe fn install(store_ptr: *mut StoreInner) -> StoreInstallGuard {
let store_id = unsafe { store_ptr.as_ref().unwrap().objects.id() };
debug_assert!(
!Self::is_active(store_id)
|| STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
let active =
unsafe { stack.last().unwrap().entry.get().as_ref().unwrap().as_ptr() };
active == store_ptr
}),
"Store context pointer mismatch"
);
if Self::active_is_async(store_id) {
return StoreInstallGuard { store_id: None };
}
Self::push(store_id, StoreContextEntry::Sync(store_ptr));
StoreInstallGuard {
store_id: Some(store_id),
}
}
pub(crate) unsafe fn pause(id: StoreId) -> StorePtrPauseGuard {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, id, "Mismatched store context access");
let ref_count_decremented = if top.borrow_count > 0 {
top.borrow_count -= 1;
true
} else {
false
};
StorePtrPauseGuard {
store_id: id,
ptr: unsafe { top.entry.get().as_ref().unwrap().as_ptr() },
ref_count_decremented,
}
})
}
pub(crate) fn try_get_current_unborrowed() -> Option<StorePtrWrapper> {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack.last_mut()?;
if top.borrow_count != 0 {
return None;
}
top.borrow_count += 1;
Some(StorePtrWrapper {
store_ptr: unsafe { top.entry.get().as_mut().unwrap().as_ptr() },
})
})
}
pub(crate) unsafe fn get_current(id: StoreId) -> StorePtrWrapper {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, id, "Mismatched store context access");
top.borrow_count += 1;
StorePtrWrapper {
store_ptr: unsafe { top.entry.get().as_mut().unwrap().as_ptr() },
}
})
}
pub(crate) unsafe fn get_current_transient(id: StoreId) -> *mut StoreInner {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, id, "Mismatched store context access");
unsafe { top.entry.get().as_mut().unwrap().as_ptr() }
})
}
pub(crate) unsafe fn try_get_current(id: StoreId) -> Option<StorePtrWrapper> {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack.last_mut()?;
if top.id != id {
return None;
}
top.borrow_count += 1;
Some(StorePtrWrapper {
store_ptr: unsafe { top.entry.get().as_mut().unwrap().as_ptr() },
})
})
}
#[cfg(feature = "experimental-async")]
pub(crate) unsafe fn try_get_current_async(id: StoreId) -> GetStoreAsyncGuardResult {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let Some(top) = stack.last_mut() else {
return GetStoreAsyncGuardResult::NotInstalled;
};
if top.id != id {
return GetStoreAsyncGuardResult::NotInstalled;
}
top.borrow_count += 1;
match unsafe { top.entry.get().as_mut().unwrap() } {
StoreContextEntry::Async(guard) => {
GetStoreAsyncGuardResult::Ok(StoreAsyncGuardWrapper {
guard: guard as *mut _,
})
}
StoreContextEntry::Sync(ptr) => {
GetStoreAsyncGuardResult::NotAsync(StorePtrWrapper { store_ptr: *ptr })
}
}
})
}
}
#[cfg(feature = "unsafe-cothread")]
pub struct CoroutineStoreGuard<'a> {
store_id: StoreId,
_store: std::marker::PhantomData<&'a mut StoreInner>,
}
#[cfg(feature = "unsafe-cothread")]
impl<'a> CoroutineStoreGuard<'a> {
pub(crate) unsafe fn new(store: &'a mut StoreInner) -> Self {
let store_id = store.objects.id();
assert!(
!StoreContext::is_active(store_id) && !StoreContext::is_suspended(store_id),
"store is already on the current thread's context stack (active or suspended)"
);
StoreContext::push_cothread(store_id, NonNull::from(store));
Self {
store_id,
_store: std::marker::PhantomData,
}
}
}
#[cfg(feature = "unsafe-cothread")]
impl Drop for CoroutineStoreGuard<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
StoreContext::uninstall_cothread(self.store_id);
}
}
impl StorePtrWrapper {
pub(crate) fn as_ref(&self) -> StoreRef<'_> {
unsafe { self.store_ptr.as_ref().unwrap().as_store_ref() }
}
pub(crate) fn as_mut(&mut self) -> StoreMut<'_> {
unsafe { self.store_ptr.as_mut().unwrap().as_store_mut() }
}
}
impl Clone for StorePtrWrapper {
fn clone(&self) -> Self {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
match unsafe { top.entry.get().as_ref().unwrap() } {
StoreContextEntry::Sync(ptr) if *ptr == self.store_ptr => (),
_ => panic!("Mismatched store context access"),
}
top.borrow_count += 1;
Self {
store_ptr: self.store_ptr,
}
})
}
}
impl Drop for StorePtrWrapper {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
let id = self.as_mut().objects_mut().id();
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, id, "Mismatched store context reinstall");
top.borrow_count -= 1;
})
}
}
#[cfg(feature = "experimental-async")]
impl Drop for StoreAsyncGuardWrapper {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
let id = unsafe { self.guard.as_ref().unwrap().objects.id() };
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, id, "Mismatched store context reinstall");
top.borrow_count -= 1;
})
}
}
impl Drop for StoreInstallGuard {
fn drop(&mut self) {
let Some(store_id) = self.store_id else {
return;
};
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
match (stack.pop(), std::thread::panicking()) {
(Some(top), false) => {
assert_eq!(top.id, store_id, "Mismatched store context uninstall");
assert_eq!(
top.borrow_count, 0,
"Cannot uninstall store context while it is still borrowed"
);
}
(Some(top), true) => {
if top.id != store_id {
stack.push(top);
}
}
(None, false) => panic!("Store context stack underflow"),
(None, true) => {
}
}
})
}
}
impl Drop for ForcedStoreInstallGuard {
fn drop(&mut self) {
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
match (stack.pop(), std::thread::panicking()) {
(Some(top), false) => {
assert_eq!(top.id, self.store_id, "Mismatched store context uninstall");
assert_eq!(
top.borrow_count, 0,
"Cannot uninstall store context while it is still borrowed"
);
}
(Some(top), true) => {
if top.id != self.store_id {
stack.push(top);
}
}
(None, false) => panic!("Store context stack underflow"),
(None, true) => {
}
}
})
}
}
impl Drop for StorePtrPauseGuard {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
STORE_CONTEXT_STACK.with(|cell| {
let mut stack = cell.borrow_mut();
let top = stack
.last_mut()
.expect("No store context installed on this thread");
assert_eq!(top.id, self.store_id, "Mismatched store context access");
assert_eq!(
unsafe { top.entry.get().as_ref().unwrap() }.as_ptr(),
self.ptr,
"Mismatched store context access"
);
if self.ref_count_decremented {
top.borrow_count += 1;
}
})
}
}
#[cfg(test)]
mod borrow_provenance {
use super::*;
use crate::{AsStoreMut, Store};
#[test]
fn nested_call_keeps_the_outer_borrow_usable() {
let mut store = Store::default();
let id = store.id();
let mut caller = store.as_store_mut();
let install = unsafe { StoreContext::install(caller.as_store_mut().inner as *mut _) };
let mut wrapper = unsafe { StoreContext::get_current(id) };
let mut shim = wrapper.as_mut();
let _ = shim.objects_mut().id();
{
let inner_install =
unsafe { StoreContext::install(shim.as_store_mut().inner as *mut _) };
let pause = unsafe { StoreContext::pause(id) };
let mut inner_wrapper = unsafe { StoreContext::get_current(id) };
let mut inner_shim = inner_wrapper.as_mut();
let _ = inner_shim.objects_mut().id();
drop(inner_wrapper);
drop(pause);
drop(inner_install);
}
let _ = shim.objects_mut().id();
drop(wrapper);
drop(install);
}
#[test]
fn a_lend_keeps_the_lending_borrow_usable() {
let mut store = Store::default();
let id = store.id();
let mut caller = store.as_store_mut();
let install = unsafe { StoreContext::install(caller.as_store_mut().inner as *mut _) };
let mut wrapper = unsafe { StoreContext::get_current(id) };
let mut shim = wrapper.as_mut();
let _ = shim.objects_mut().id();
shim.parked(|| {
crate::Store::with_current(|lent| {
let _ = lent.objects_mut().id();
})
.expect("a parked store is lendable");
});
let _ = shim.objects_mut().id();
drop(wrapper);
drop(install);
}
}