use crate::prelude::*;
use crate::runtime::vm::imports::Imports;
use crate::runtime::vm::instance::{Instance, InstanceHandle};
use crate::runtime::vm::memory::Memory;
use crate::runtime::vm::mpk::ProtectionKey;
use crate::runtime::vm::table::Table;
use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo};
use crate::store::{InstanceId, StoreOpaque, StoreResourceLimiter};
use core::future::Future;
use core::mem;
use core::pin::Pin;
use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, MemoryKind, Module, VMOffsets,
};
#[cfg(feature = "gc")]
use crate::runtime::vm::{GcHeap, GcRuntime};
#[cfg(feature = "component-model")]
use wasmtime_environ::{
StaticModuleIndex,
component::{Component, VMComponentOffsets},
};
mod on_demand;
pub use self::on_demand::OnDemandInstanceAllocator;
#[cfg(feature = "pooling-allocator")]
mod pooling;
#[cfg(feature = "pooling-allocator")]
pub use self::pooling::{
InstanceLimits, PoolConcurrencyLimitError, PoolingAllocatorMetrics, PoolingInstanceAllocator,
PoolingInstanceAllocatorConfig,
};
pub struct InstanceAllocationRequest<'a, 'b> {
pub id: InstanceId,
pub runtime_info: &'a ModuleRuntimeInfo,
pub imports: Imports<'a>,
pub store: &'a StoreOpaque,
pub limiter: Option<&'a mut StoreResourceLimiter<'b>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct MemoryAllocationIndex(u32);
impl Default for MemoryAllocationIndex {
fn default() -> Self {
MemoryAllocationIndex(u32::MAX)
}
}
impl MemoryAllocationIndex {
#[cfg(feature = "pooling-allocator")]
pub fn index(&self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct TableAllocationIndex(u32);
impl Default for TableAllocationIndex {
fn default() -> Self {
TableAllocationIndex(u32::MAX)
}
}
impl TableAllocationIndex {
#[cfg(feature = "pooling-allocator")]
pub fn index(&self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct GcHeapAllocationIndex(u32);
impl Default for GcHeapAllocationIndex {
fn default() -> Self {
GcHeapAllocationIndex(u32::MAX)
}
}
impl GcHeapAllocationIndex {
pub fn index(&self) -> usize {
self.0 as usize
}
}
pub unsafe trait InstanceAllocator: Send + Sync {
#[cfg(feature = "component-model")]
fn validate_component<'a>(
&self,
component: &Component,
offsets: &VMComponentOffsets<HostPtr>,
get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
) -> Result<()>;
fn validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()>;
#[cfg(feature = "gc")]
fn validate_memory(&self, memory: &wasmtime_environ::Memory) -> Result<()>;
#[cfg(feature = "component-model")]
fn increment_component_instance_count(&self) -> Result<()>;
#[cfg(feature = "component-model")]
fn decrement_component_instance_count(&self);
fn increment_core_instance_count(&self) -> Result<()>;
fn decrement_core_instance_count(&self);
fn allocate_memory<'a, 'b: 'a, 'c: 'a>(
&'a self,
request: &'a mut InstanceAllocationRequest<'b, 'c>,
ty: &'a wasmtime_environ::Memory,
memory_index: Option<DefinedMemoryIndex>,
memory_kind: MemoryKind,
) -> Pin<Box<dyn Future<Output = Result<(MemoryAllocationIndex, Memory)>> + Send + 'a>>;
unsafe fn deallocate_memory(
&self,
memory_index: Option<DefinedMemoryIndex>,
allocation_index: MemoryAllocationIndex,
memory: Memory,
);
fn allocate_table<'a, 'b: 'a, 'c: 'a>(
&'a self,
req: &'a mut InstanceAllocationRequest<'b, 'c>,
table: &'a wasmtime_environ::Table,
table_index: DefinedTableIndex,
) -> Pin<Box<dyn Future<Output = Result<(TableAllocationIndex, Table)>> + Send + 'a>>;
unsafe fn deallocate_table(
&self,
table_index: DefinedTableIndex,
allocation_index: TableAllocationIndex,
table: Table,
);
#[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack>;
#[cfg(feature = "async")]
unsafe fn deallocate_fiber_stack(&self, stack: wasmtime_fiber::FiberStack);
#[cfg(feature = "gc")]
fn allocate_gc_heap(
&self,
engine: &crate::Engine,
gc_runtime: &dyn GcRuntime,
memory_alloc_index: MemoryAllocationIndex,
memory: Memory,
) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)>;
#[cfg(feature = "gc")]
#[must_use = "it is the caller's responsibility to deallocate the GC heap's underlying memory \
storage after the GC heap is deallocated"]
fn deallocate_gc_heap(
&self,
allocation_index: GcHeapAllocationIndex,
gc_heap: Box<dyn GcHeap>,
) -> (MemoryAllocationIndex, Memory);
fn purge_module(&self, module: CompiledModuleId);
fn next_available_pkey(&self) -> Option<ProtectionKey>;
fn restrict_to_pkey(&self, pkey: ProtectionKey);
fn allow_all_pkeys(&self);
#[cfg(feature = "pooling-allocator")]
fn as_pooling(&self) -> Option<&PoolingInstanceAllocator> {
None
}
}
impl dyn InstanceAllocator + '_ {
pub(crate) async unsafe fn allocate_module(
&self,
mut request: InstanceAllocationRequest<'_, '_>,
) -> Result<InstanceHandle> {
let module = request.runtime_info.env_module();
if cfg!(debug_assertions) {
InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
}
let num_defined_memories = module.num_defined_memories();
let num_defined_tables = module.num_defined_tables();
let memories = TryPrimaryMap::with_capacity(num_defined_memories)?;
let tables = TryPrimaryMap::with_capacity(num_defined_tables)?;
self.increment_core_instance_count()?;
let mut guard = DeallocateOnDrop {
run_deallocate: true,
memories,
tables,
allocator: self,
};
self.allocate_memories(&mut request, &mut guard.memories)
.await?;
self.allocate_tables(&mut request, &mut guard.tables)
.await?;
let handle = unsafe { Instance::new(request, &mut guard.memories, &mut guard.tables)? };
guard.run_deallocate = false;
return Ok(handle);
struct DeallocateOnDrop<'a> {
run_deallocate: bool,
memories: TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
tables: TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
allocator: &'a (dyn InstanceAllocator + 'a),
}
impl Drop for DeallocateOnDrop<'_> {
fn drop(&mut self) {
if !self.run_deallocate {
debug_assert!(self.memories.is_empty());
debug_assert!(self.tables.is_empty());
return;
}
unsafe {
self.allocator.deallocate_memories(&mut self.memories);
self.allocator.deallocate_tables(&mut self.tables);
}
self.allocator.decrement_core_instance_count();
}
}
}
pub(crate) unsafe fn deallocate_module(&self, handle: &mut InstanceHandle) {
unsafe {
self.deallocate_memories(handle.get_mut().memories_mut());
self.deallocate_tables(handle.get_mut().tables_mut());
}
self.decrement_core_instance_count();
}
async fn allocate_memories(
&self,
request: &mut InstanceAllocationRequest<'_, '_>,
memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
) -> Result<()> {
let module = request.runtime_info.env_module();
if cfg!(debug_assertions) {
InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
}
for (memory_index, ty) in module.memories.iter().skip(module.num_imported_memories) {
let memory_index = module
.defined_memory_index(memory_index)
.expect("should be a defined memory since we skipped imported ones");
let memory = self
.allocate_memory(request, ty, Some(memory_index), MemoryKind::LinearMemory)
.await?;
memories.push(memory)?;
}
Ok(())
}
unsafe fn deallocate_memories(
&self,
memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
) {
for (memory_index, (allocation_index, memory)) in mem::take(memories) {
unsafe {
self.deallocate_memory(Some(memory_index), allocation_index, memory);
}
}
}
async fn allocate_tables(
&self,
request: &mut InstanceAllocationRequest<'_, '_>,
tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
) -> Result<()> {
let module = request.runtime_info.env_module();
if cfg!(debug_assertions) {
InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
}
for (index, table) in module.tables.iter().skip(module.num_imported_tables) {
let def_index = module
.defined_table_index(index)
.expect("should be a defined table since we skipped imported ones");
let table = self.allocate_table(request, table, def_index).await?;
tables.push(table)?;
}
Ok(())
}
unsafe fn deallocate_tables(
&self,
tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
) {
for (table_index, (allocation_index, table)) in mem::take(tables) {
unsafe {
self.deallocate_table(table_index, allocation_index, table);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocator_traits_are_object_safe() {
fn _instance_allocator(_: &dyn InstanceAllocator) {}
}
}