use super::PoolingInstanceAllocator;
use crate::vm::sys::vm::{decommit_pages, iovec};
use crate::vm::{MemoryAllocationIndex, MemoryImageSlot, Table, TableAllocationIndex};
use smallvec::SmallVec;
use std::io;
#[cfg(feature = "async")]
use wasmtime_fiber::FiberStack;
#[repr(transparent)]
struct IoVec(iovec);
unsafe impl Send for IoVec {}
unsafe impl Sync for IoVec {}
impl std::fmt::Debug for IoVec {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IoVec")
.field("base", &self.0.iov_base)
.field("len", &self.0.iov_len)
.finish()
}
}
#[cfg(feature = "async")]
struct SendSyncStack(FiberStack);
#[cfg(feature = "async")]
unsafe impl Send for SendSyncStack {}
#[cfg(feature = "async")]
unsafe impl Sync for SendSyncStack {}
#[derive(Default)]
pub struct DecommitQueue {
raw: SmallVec<[IoVec; 2]>,
memories: SmallVec<[(MemoryAllocationIndex, MemoryImageSlot, usize); 1]>,
tables: SmallVec<[(TableAllocationIndex, Table, usize); 1]>,
#[cfg(feature = "async")]
stacks: SmallVec<[(SendSyncStack, usize); 1]>,
}
impl std::fmt::Debug for DecommitQueue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecommitQueue")
.field("raw", &self.raw)
.finish_non_exhaustive()
}
}
impl DecommitQueue {
pub fn append(
&mut self,
Self {
raw,
memories,
tables,
#[cfg(feature = "async")]
stacks,
}: &mut Self,
) {
self.raw.append(raw);
self.memories.append(memories);
self.tables.append(tables);
#[cfg(feature = "async")]
self.stacks.append(stacks);
}
pub fn raw_len(&self) -> usize {
self.raw.len()
}
pub unsafe fn push_raw(&mut self, ptr: *mut u8, len: usize) {
self.raw.push(IoVec(iovec {
iov_base: ptr.cast(),
iov_len: len,
}));
}
pub unsafe fn push_memory(
&mut self,
allocation_index: MemoryAllocationIndex,
image: MemoryImageSlot,
bytes_resident: usize,
) {
self.memories
.push((allocation_index, image, bytes_resident));
}
pub unsafe fn push_table(
&mut self,
allocation_index: TableAllocationIndex,
table: Table,
bytes_resident: usize,
) {
self.tables.push((allocation_index, table, bytes_resident));
}
#[cfg(feature = "async")]
pub unsafe fn push_stack(&mut self, stack: FiberStack, bytes_resident: usize) {
self.stacks.push((SendSyncStack(stack), bytes_resident));
}
fn decommit_all_raw(&mut self) -> io::Result<()> {
let iov: &[IoVec] = self.raw.as_slice();
let iov = unsafe { &*(iov as *const [IoVec] as *const [iovec]) };
unsafe {
decommit_pages(iov)?;
}
self.raw.clear();
Ok(())
}
pub fn flush(mut self, pool: &PoolingInstanceAllocator) -> bool {
let decommit_succeeded = self.decommit_all_raw().is_ok();
let mut deallocated_any = false;
if !self.memories.is_empty() {
deallocated_any = true;
unsafe {
pool.memories.deallocate_many(self.memories.into_iter().map(
|(allocation_index, image, bytes_resident)| {
let image = if decommit_succeeded {
Some(image)
} else {
None
};
(allocation_index, image, bytes_resident)
},
));
}
}
if !self.tables.is_empty() {
deallocated_any = true;
unsafe {
pool.tables.deallocate_many(self.tables.into_iter().map(
|(allocation_index, mut table, bytes_resident)| {
if !decommit_succeeded {
table.manually_memset_zeros();
}
(allocation_index, table, bytes_resident)
},
));
}
}
#[cfg(feature = "async")]
if !self.stacks.is_empty() {
deallocated_any = true;
unsafe {
pool.stacks.deallocate_many(
self.stacks
.into_iter()
.map(|(stack, bytes_resident)| (stack.0, bytes_resident)),
);
}
}
deallocated_any
}
}