use wasmtime::{Caller, Val};
use crate::runtime::{
StoreData, fuel,
host::{fatal_host_error, range_error},
limits::HostBytes,
};
const MAX_ELEMENTS: usize = 32 * 1024 * 1024;
pub(super) struct Buffer<T> {
values: Vec<T>,
reservation: Option<HostBytes>,
}
impl<T: Copy> Buffer<T> {
pub(super) fn new() -> Self {
Self {
values: Vec::new(),
reservation: None,
}
}
pub(super) fn append(
&mut self,
caller: &mut Caller<'_, StoreData>,
values: &[T],
) -> wasmtime::Result<()> {
fuel::charge(&mut *caller, fuel::COPY, values.len() as u64)?;
self.reserve(caller, values.len())?;
self.values.extend_from_slice(values);
Ok(())
}
fn reserve(
&mut self,
caller: &mut Caller<'_, StoreData>,
additional: usize,
) -> wasmtime::Result<()> {
let needed = self
.values
.len()
.checked_add(additional)
.filter(|count| *count <= MAX_ELEMENTS)
.ok_or_else(|| range_error("String operation result exceeds resource limit"))?;
if needed <= self.values.capacity() {
return Ok(());
}
let capacity = self
.values
.capacity()
.saturating_mul(2)
.max(needed)
.min(MAX_ELEMENTS);
let bytes = capacity
.checked_mul(std::mem::size_of::<T>())
.ok_or_else(|| fatal_host_error("String operation output size overflow"))?;
let reservation = HostBytes::new(&caller.data().tenant_limits, bytes as u64)?;
self.values
.try_reserve_exact(capacity - self.values.len())
.map_err(fatal_host_error)?;
self.reservation = Some(reservation);
Ok(())
}
pub(super) fn values(&self) -> &[T] {
&self.values
}
}
impl Buffer<Val> {
pub(super) fn push(
&mut self,
caller: &mut Caller<'_, StoreData>,
value: Val,
) -> wasmtime::Result<()> {
fuel::charge(&mut *caller, fuel::ELEM, 1)?;
self.reserve(caller, 1)?;
self.values.push(value);
Ok(())
}
}