use super::limits::{DEFAULT_MEMORY_CEILING_BYTES, DEFAULT_TABLE_CEILING_ELEMS};
use super::{ResourceLimiter, ResourceLimiterInner, Store, PAGE_SIZE};
use crate::extern_::{Memory, Table};
use crate::value::Ref;
use crate::{Error, Result};
fn bytes(pages: u64) -> usize {
(pages as usize).saturating_mul(PAGE_SIZE)
}
impl<T: 'static> Store<T> {
#[allow(clippy::unnecessary_wraps)]
fn with_sync_limiter<R>(
&mut self,
f: impl FnOnce(&mut dyn ResourceLimiter) -> R,
) -> Result<Option<R>> {
match self.limiter.as_mut() {
None => Ok(None),
Some(ResourceLimiterInner::Sync(l)) => Ok(Some(f(l(&mut self.data)))),
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(_)) => Err(Error::msg(
"an async resource limiter requires an async context (use the `*_async` API)",
)),
}
}
pub(crate) fn limiter_memories(&mut self) -> Option<usize> {
match self.limiter.as_mut()? {
ResourceLimiterInner::Sync(l) => Some(l(&mut self.data).memories()),
#[cfg(feature = "async")]
ResourceLimiterInner::Async(l) => Some(l(&mut self.data).memories()),
}
}
pub(crate) fn limiter_tables(&mut self) -> Option<usize> {
match self.limiter.as_mut()? {
ResourceLimiterInner::Sync(l) => Some(l(&mut self.data).tables()),
#[cfg(feature = "async")]
ResourceLimiterInner::Async(l) => Some(l(&mut self.data).tables()),
}
}
pub(crate) fn limiter_instances(&mut self) -> Option<usize> {
match self.limiter.as_mut()? {
ResourceLimiterInner::Sync(l) => Some(l(&mut self.data).instances()),
#[cfg(feature = "async")]
ResourceLimiterInner::Async(l) => Some(l(&mut self.data).instances()),
}
}
fn memory_grow_failed(&mut self) -> Result<()> {
let err = Error::msg("failed to grow memory");
match self.limiter.as_mut() {
None => Ok(()),
Some(ResourceLimiterInner::Sync(l)) => l(&mut self.data).memory_grow_failed(err),
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(l)) => l(&mut self.data).memory_grow_failed(err),
}
}
pub(crate) fn grow_memory(&mut self, handle: Memory, delta: u64) -> Result<Option<u64>> {
let (current, max) = {
let e = self.inner.memory(handle);
(e.size_pages(), e.ty.maximum())
};
let desired = current.saturating_add(delta);
let allowed = self
.with_sync_limiter(|l| {
l.memory_growing(bytes(current), bytes(desired), max.map(bytes))
})?
.transpose()?
.unwrap_or(bytes(desired) <= DEFAULT_MEMORY_CEILING_BYTES);
if allowed {
if let Some(old) = self.inner.memory_mut(handle).grow(delta) {
return Ok(Some(old));
}
}
self.memory_grow_failed()?;
Ok(None)
}
pub(crate) fn grow_table(
&mut self,
handle: Table,
delta: u64,
init: Ref,
) -> Result<Option<u64>> {
let (current, max) = {
let e = self.inner.table(handle);
(e.size() as usize, e.ty.maximum().map(|m| m as usize))
};
let desired = current.saturating_add(delta as usize);
let allowed = self
.with_sync_limiter(|l| l.table_growing(current, desired, max))?
.transpose()?
.unwrap_or(desired <= DEFAULT_TABLE_CEILING_ELEMS);
if allowed {
if let Some(old) = self.inner.table_mut(handle).grow(delta, init) {
return Ok(Some(old));
}
}
let err = Error::msg("failed to grow table");
match self.limiter.as_mut() {
None => {}
Some(ResourceLimiterInner::Sync(l)) => l(&mut self.data).table_grow_failed(err)?,
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(l)) => l(&mut self.data).table_grow_failed(err)?,
}
Ok(None)
}
pub(crate) fn grow_gc_reservation(&mut self, target: usize, bytes_needed: u64) -> Result<()> {
let current = self.inner.gc.reserved();
if target <= current {
return Ok(());
}
let allowed = match self.with_sync_limiter(|l| l.memory_growing(current, target, None))? {
Some(decision) => decision?,
None => target <= super::gc::ABORT_SAFETY_CAP,
};
if !allowed {
return Err(crate::gc::GcHeapOutOfMemory::new((), bytes_needed).into());
}
let granted = self.inner.gc.grant(target);
self.inner.engine().add_gc_committed(granted);
Ok(())
}
pub(crate) fn gc_reserve_host(&mut self, charge: usize) -> Result<()> {
if self.inner.gc.fits(charge) {
return Ok(());
}
if !self
.inner
.gc
.is_free_grant(self.inner.gc.desired_reservation(charge))
&& self.inner.gc.is_collecting()
{
let roots = self.inner.exec_roots();
self.inner.gc_collect(&roots);
if self.inner.gc.fits(charge) {
return Ok(());
}
}
let target = self.inner.gc.desired_reservation(charge);
if self.inner.gc.is_free_grant(target) {
let granted = self.inner.gc.grant(target);
self.inner.engine().add_gc_committed(granted);
return Ok(());
}
self.grow_gc_reservation(target, charge as u64)
}
pub(crate) fn gc_alloc_externref(
&mut self,
value: Box<dyn core::any::Any + Send + Sync>,
) -> Result<u32> {
self.gc_reserve_host(super::entity::extern_charge())?;
self.inner.alloc_externref(value)
}
pub(crate) fn gc_alloc_exn(
&mut self,
entity: super::ExnEntity,
) -> Result<crate::value::Rooted<crate::value::ExnRef>> {
self.gc_reserve_host(entity.byte_size())?;
self.inner.alloc_exn(entity)
}
pub(crate) fn limiter_allows_memory(&mut self, initial: u64, max: Option<u64>) -> Result<bool> {
Ok(self
.with_sync_limiter(|l| l.memory_growing(0, bytes(initial), max.map(bytes)))?
.transpose()?
.unwrap_or(bytes(initial) <= DEFAULT_MEMORY_CEILING_BYTES))
}
pub(crate) fn limiter_allows_table(&mut self, initial: u64, max: Option<u64>) -> Result<bool> {
Ok(self
.with_sync_limiter(|l| l.table_growing(0, initial as usize, max.map(|m| m as usize)))?
.transpose()?
.unwrap_or(initial as usize <= DEFAULT_TABLE_CEILING_ELEMS))
}
}
#[cfg(feature = "async")]
impl<T: 'static> Store<T> {
async fn memory_growing_async(
&mut self,
current: usize,
desired: usize,
max: Option<usize>,
) -> Result<bool> {
match &mut self.limiter {
None => Ok(desired <= DEFAULT_MEMORY_CEILING_BYTES),
Some(ResourceLimiterInner::Sync(l)) => {
l(&mut self.data).memory_growing(current, desired, max)
}
Some(ResourceLimiterInner::Async(l)) => {
l(&mut self.data)
.memory_growing(current, desired, max)
.await
}
}
}
pub(crate) async fn grow_memory_async(
&mut self,
handle: Memory,
delta: u64,
) -> Result<Option<u64>> {
let (current, max) = {
let e = self.inner.memory(handle);
(e.size_pages(), e.ty.maximum())
};
let desired = current.saturating_add(delta);
let allowed = self
.memory_growing_async(bytes(current), bytes(desired), max.map(bytes))
.await?;
if allowed {
if let Some(old) = self.inner.memory_mut(handle).grow(delta) {
return Ok(Some(old));
}
}
self.memory_grow_failed()?;
Ok(None)
}
pub(crate) async fn limiter_allows_memory_async(
&mut self,
initial: u64,
max: Option<u64>,
) -> Result<bool> {
self.memory_growing_async(0, bytes(initial), max.map(bytes))
.await
}
async fn table_growing_async(
&mut self,
current: usize,
desired: usize,
max: Option<usize>,
) -> Result<bool> {
match &mut self.limiter {
None => Ok(desired <= DEFAULT_TABLE_CEILING_ELEMS),
Some(ResourceLimiterInner::Sync(l)) => {
l(&mut self.data).table_growing(current, desired, max)
}
Some(ResourceLimiterInner::Async(l)) => {
l(&mut self.data).table_growing(current, desired, max).await
}
}
}
pub(crate) async fn limiter_allows_table_async(
&mut self,
initial: u64,
max: Option<u64>,
) -> Result<bool> {
self.table_growing_async(0, initial as usize, max.map(|m| m as usize))
.await
}
pub(crate) async fn grow_table_async(
&mut self,
handle: Table,
delta: u64,
init: Ref,
) -> Result<Option<u64>> {
let (current, max) = {
let e = self.inner.table(handle);
(e.size() as usize, e.ty.maximum().map(|m| m as usize))
};
let desired = current.saturating_add(delta as usize);
let allowed = self.table_growing_async(current, desired, max).await?;
if allowed {
if let Some(old) = self.inner.table_mut(handle).grow(delta, init) {
return Ok(Some(old));
}
}
let err = Error::msg("failed to grow table");
match self.limiter.as_mut() {
None => {}
Some(ResourceLimiterInner::Sync(l)) => l(&mut self.data).table_grow_failed(err)?,
Some(ResourceLimiterInner::Async(l)) => l(&mut self.data).table_grow_failed(err)?,
}
Ok(None)
}
pub(crate) async fn gc_reserve_host_async(&mut self, charge: usize) -> Result<()> {
if self.inner.gc.fits(charge) {
return Ok(());
}
if !self
.inner
.gc
.is_free_grant(self.inner.gc.desired_reservation(charge))
&& self.inner.gc.is_collecting()
{
let roots = self.inner.exec_roots();
self.inner.gc_collect(&roots);
if self.inner.gc.fits(charge) {
return Ok(());
}
}
let target = self.inner.gc.desired_reservation(charge);
if self.inner.gc.is_free_grant(target) {
let granted = self.inner.gc.grant(target);
self.inner.engine().add_gc_committed(granted);
return Ok(());
}
let current = self.inner.gc.reserved();
let allowed = match &mut self.limiter {
None => target <= super::gc::ABORT_SAFETY_CAP,
Some(ResourceLimiterInner::Sync(l)) => {
l(&mut self.data).memory_growing(current, target, None)?
}
Some(ResourceLimiterInner::Async(l)) => {
l(&mut self.data)
.memory_growing(current, target, None)
.await?
}
};
if !allowed {
return Err(crate::gc::GcHeapOutOfMemory::new((), charge as u64).into());
}
let granted = self.inner.gc.grant(target);
self.inner.engine().add_gc_committed(granted);
Ok(())
}
}