use std::{
borrow::BorrowMut,
cell::{RefCell, UnsafeCell},
mem::MaybeUninit,
};
#[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) enum StoreInstallGuard {
Installed(StoreId),
NotInstalled,
}
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 install(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),
});
})
}
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::install(store_id, StoreContextEntry::Async(guard));
ForcedStoreInstallGuard { store_id }
}
pub(crate) unsafe fn ensure_installed(store_ptr: *mut StoreInner) -> StoreInstallGuard {
let store_id = unsafe { store_ptr.as_ref().unwrap().objects.id() };
if Self::is_active(store_id) {
let current_ptr = STORE_CONTEXT_STACK.with(|cell| {
let stack = cell.borrow();
unsafe { stack.last().unwrap().entry.get().as_ref().unwrap().as_ptr() }
});
assert_eq!(store_ptr, current_ptr, "Store context pointer mismatch");
StoreInstallGuard::NotInstalled
} else {
Self::install(store_id, StoreContextEntry::Sync(store_ptr));
StoreInstallGuard::Installed(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) 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 })
}
}
})
}
}
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) {
if let Self::Installed(store_id) = 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, *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;
}
})
}
}