use crate::imports::Imports;
use crate::instance::{Instance, InstanceHandle, RuntimeMemoryCreator};
use crate::memory::{DefaultMemoryCreator, Memory};
use crate::table::Table;
use crate::{CompiledModuleId, ModuleRuntimeInfo, Store};
use anyhow::{anyhow, bail, Result};
use std::alloc;
use std::any::Any;
use std::convert::TryFrom;
use std::ptr;
use std::sync::Arc;
use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets,
WasmType, WASM_PAGE_SIZE,
};
#[cfg(feature = "pooling-allocator")]
mod pooling;
#[cfg(feature = "pooling-allocator")]
pub use self::pooling::{
InstanceLimits, PoolingAllocationStrategy, PoolingInstanceAllocator,
PoolingInstanceAllocatorConfig,
};
pub struct InstanceAllocationRequest<'a> {
pub runtime_info: &'a Arc<dyn ModuleRuntimeInfo>,
pub imports: Imports<'a>,
pub host_state: Box<dyn Any + Send + Sync>,
pub store: StorePtr,
}
pub struct StorePtr(Option<*mut dyn Store>);
impl StorePtr {
pub fn empty() -> Self {
Self(None)
}
pub fn new(ptr: *mut dyn Store) -> Self {
Self(Some(ptr))
}
pub fn as_raw(&self) -> Option<*mut dyn Store> {
self.0.clone()
}
pub(crate) unsafe fn get(&mut self) -> Option<&mut dyn Store> {
match self.0 {
Some(ptr) => Some(&mut *ptr),
None => None,
}
}
}
pub unsafe trait InstanceAllocator: Send + Sync {
fn validate(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
drop((module, offsets));
Ok(())
}
unsafe fn allocate(&self, req: InstanceAllocationRequest) -> Result<InstanceHandle>;
unsafe fn initialize(
&self,
handle: &mut InstanceHandle,
module: &Module,
is_bulk_memory: bool,
) -> Result<()>;
unsafe fn deallocate(&self, handle: &InstanceHandle);
#[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);
fn purge_module(&self, module: CompiledModuleId);
}
fn get_table_init_start(init: &TableInitializer, instance: &Instance) -> Result<u32> {
match init.base {
Some(base) => {
let val = unsafe {
if let Some(def_index) = instance.module().defined_global_index(base) {
*instance.global(def_index).as_u32()
} else {
*(*instance.imported_global(base).from).as_u32()
}
};
init.offset
.checked_add(val)
.ok_or_else(|| anyhow!("element segment global base overflows"))
}
None => Ok(init.offset),
}
}
fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
match &module.table_initialization {
TableInitialization::FuncTable { segments, .. }
| TableInitialization::Segments { segments } => {
for segment in segments {
let table = unsafe { &*instance.get_table(segment.table_index) };
let start = get_table_init_start(segment, instance)?;
let start = usize::try_from(start).unwrap();
let end = start.checked_add(segment.elements.len());
match end {
Some(end) if end <= table.size() as usize => {
}
_ => {
bail!("table out of bounds: elements segment does not fit")
}
}
}
}
}
Ok(())
}
fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
match &module.table_initialization {
TableInitialization::FuncTable { segments, .. }
| TableInitialization::Segments { segments } => {
for segment in segments {
instance.table_init_segment(
segment.table_index,
&segment.elements,
get_table_init_start(segment, instance)?,
0,
segment.elements.len() as u32,
)?;
}
}
}
Ok(())
}
fn get_memory_init_start(init: &MemoryInitializer, instance: &Instance) -> Result<u64> {
match init.base {
Some(base) => {
let mem64 = instance.module().memory_plans[init.memory_index]
.memory
.memory64;
let val = unsafe {
let global = if let Some(def_index) = instance.module().defined_global_index(base) {
instance.global(def_index)
} else {
&*instance.imported_global(base).from
};
if mem64 {
*global.as_u64()
} else {
u64::from(*global.as_u32())
}
};
init.offset
.checked_add(val)
.ok_or_else(|| anyhow!("data segment global base overflows"))
}
None => Ok(init.offset),
}
}
fn check_memory_init_bounds(instance: &Instance, initializers: &[MemoryInitializer]) -> Result<()> {
for init in initializers {
let memory = instance.get_memory(init.memory_index);
let start = get_memory_init_start(init, instance)?;
let end = usize::try_from(start)
.ok()
.and_then(|start| start.checked_add(init.data.len()));
match end {
Some(end) if end <= memory.current_length() => {
}
_ => {
bail!("memory out of bounds: data segment does not fit")
}
}
}
Ok(())
}
fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<()> {
let memory_size_in_pages =
&|memory| (instance.get_memory(memory).current_length() as u64) / u64::from(WASM_PAGE_SIZE);
let get_global_as_u64 = &|global| unsafe {
let def = if let Some(def_index) = instance.module().defined_global_index(global) {
instance.global(def_index)
} else {
&*instance.imported_global(global).from
};
if module.globals[global].wasm_ty == WasmType::I64 {
*def.as_u64()
} else {
u64::from(*def.as_u32())
}
};
let ok = module.memory_initialization.init_memory(
InitMemory::Runtime {
memory_size_in_pages,
get_global_as_u64,
},
&mut |memory_index, init| {
if let Some(memory_index) = module.defined_memory_index(memory_index) {
if !instance.memories[memory_index].needs_init() {
return true;
}
}
let memory = instance.get_memory(memory_index);
unsafe {
let src = instance.wasm_data(init.data.clone());
let dst = memory.base.add(usize::try_from(init.offset).unwrap());
ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len())
}
true
},
);
if !ok {
return Err(Trap::MemoryOutOfBounds.into());
}
Ok(())
}
fn check_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
check_table_init_bounds(instance, module)?;
match &instance.module().memory_initialization {
MemoryInitialization::Segmented(initializers) => {
check_memory_init_bounds(instance, initializers)?;
}
MemoryInitialization::Static { .. } => {}
}
Ok(())
}
fn initialize_instance(
instance: &mut Instance,
module: &Module,
is_bulk_memory: bool,
) -> Result<()> {
if !is_bulk_memory {
check_init_bounds(instance, module)?;
}
initialize_tables(instance, module)?;
initialize_memories(instance, &module)?;
Ok(())
}
#[derive(Clone)]
pub struct OnDemandInstanceAllocator {
mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>,
#[cfg(feature = "async")]
stack_size: usize,
}
impl OnDemandInstanceAllocator {
pub fn new(mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>, stack_size: usize) -> Self {
drop(stack_size); Self {
mem_creator,
#[cfg(feature = "async")]
stack_size,
}
}
fn create_tables(
store: &mut StorePtr,
runtime_info: &Arc<dyn ModuleRuntimeInfo>,
) -> Result<PrimaryMap<DefinedTableIndex, Table>> {
let module = runtime_info.module();
let num_imports = module.num_imported_tables;
let mut tables: PrimaryMap<DefinedTableIndex, _> =
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
for (_, table) in module.table_plans.iter().skip(num_imports) {
tables.push(Table::new_dynamic(table, unsafe {
store
.get()
.expect("if module has table plans, store is not empty")
})?);
}
Ok(tables)
}
fn create_memories(
&self,
store: &mut StorePtr,
runtime_info: &Arc<dyn ModuleRuntimeInfo>,
) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>> {
let module = runtime_info.module();
let creator = self
.mem_creator
.as_deref()
.unwrap_or_else(|| &DefaultMemoryCreator);
let num_imports = module.num_imported_memories;
let mut memories: PrimaryMap<DefinedMemoryIndex, _> =
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
for (memory_idx, plan) in module.memory_plans.iter().skip(num_imports) {
let defined_memory_idx = module
.defined_memory_index(memory_idx)
.expect("Skipped imports, should never be None");
let image = runtime_info.memory_image(defined_memory_idx)?;
memories.push(Memory::new_dynamic(
plan,
creator,
unsafe {
store
.get()
.expect("if module has memory plans, store is not empty")
},
image,
)?);
}
Ok(memories)
}
}
impl Default for OnDemandInstanceAllocator {
fn default() -> Self {
Self {
mem_creator: None,
#[cfg(feature = "async")]
stack_size: 0,
}
}
}
pub unsafe fn allocate_single_memory_instance(
req: InstanceAllocationRequest,
memory: Memory,
) -> Result<InstanceHandle> {
let mut memories = PrimaryMap::default();
memories.push(memory);
let tables = PrimaryMap::default();
let layout = Instance::alloc_layout(req.runtime_info.offsets());
let instance = alloc::alloc(layout) as *mut Instance;
Instance::new_at(instance, layout.size(), req, memories, tables);
Ok(InstanceHandle { instance })
}
pub unsafe fn deallocate(handle: &InstanceHandle) {
let layout = Instance::alloc_layout(handle.instance().offsets());
ptr::drop_in_place(handle.instance);
alloc::dealloc(handle.instance.cast(), layout);
}
unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
unsafe fn allocate(&self, mut req: InstanceAllocationRequest) -> Result<InstanceHandle> {
let memories = self.create_memories(&mut req.store, &req.runtime_info)?;
let tables = Self::create_tables(&mut req.store, &req.runtime_info)?;
let layout = Instance::alloc_layout(req.runtime_info.offsets());
let instance_ptr = alloc::alloc(layout) as *mut Instance;
Instance::new_at(instance_ptr, layout.size(), req, memories, tables);
Ok(InstanceHandle {
instance: instance_ptr,
})
}
unsafe fn initialize(
&self,
handle: &mut InstanceHandle,
module: &Module,
is_bulk_memory: bool,
) -> Result<()> {
initialize_instance(handle.instance_mut(), module, is_bulk_memory)
}
unsafe fn deallocate(&self, handle: &InstanceHandle) {
deallocate(handle)
}
#[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
if self.stack_size == 0 {
bail!("fiber stacks are not supported by the allocator")
}
let stack = wasmtime_fiber::FiberStack::new(self.stack_size)?;
Ok(stack)
}
#[cfg(feature = "async")]
unsafe fn deallocate_fiber_stack(&self, _stack: &wasmtime_fiber::FiberStack) {
}
fn purge_module(&self, _: CompiledModuleId) {}
}