Remove some custom error types in Wasmtime (#5347)

* Remove some custom error types in Wasmtime

These types are mostly cumbersome to work with nowadays that `anyhow` is
used everywhere else. This commit removes `InstantiationError` and
`SetupError` in favor of using `anyhow::Error` throughout. This can
eventually culminate in creation of specific errors for embedders to
downcast to but for now this should be general enough.

* Fix Windows build
This commit is contained in:
Alex Crichton
2022-12-01 14:47:10 -06:00
committed by GitHub
parent 4510a4a805
commit e0b9663e44
13 changed files with 129 additions and 266 deletions

2
Cargo.lock generated
View File

@@ -3643,7 +3643,6 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
"serde", "serde",
"target-lexicon", "target-lexicon",
"thiserror",
"wasmtime-environ", "wasmtime-environ",
"wasmtime-jit-debug", "wasmtime-jit-debug",
"wasmtime-jit-icache-coherence", "wasmtime-jit-icache-coherence",
@@ -3687,7 +3686,6 @@ dependencies = [
"paste", "paste",
"rand 0.8.5", "rand 0.8.5",
"rustix", "rustix",
"thiserror",
"wasmtime-asm-macros", "wasmtime-asm-macros",
"wasmtime-environ", "wasmtime-environ",
"wasmtime-fiber", "wasmtime-fiber",

View File

@@ -14,7 +14,6 @@ edition.workspace = true
wasmtime-environ = { workspace = true } wasmtime-environ = { workspace = true }
wasmtime-jit-debug = { workspace = true, features = ["perf_jitdump"], optional = true } wasmtime-jit-debug = { workspace = true, features = ["perf_jitdump"], optional = true }
wasmtime-runtime = { workspace = true } wasmtime-runtime = { workspace = true }
thiserror = { workspace = true }
target-lexicon = { workspace = true } target-lexicon = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
cfg-if = "1.0" cfg-if = "1.0"

View File

@@ -14,39 +14,16 @@ use std::convert::TryFrom;
use std::ops::Range; use std::ops::Range;
use std::str; use std::str;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error;
use wasmtime_environ::obj; use wasmtime_environ::obj;
use wasmtime_environ::{ use wasmtime_environ::{
CompileError, DefinedFuncIndex, FuncIndex, FunctionLoc, MemoryInitialization, Module, DefinedFuncIndex, FuncIndex, FunctionLoc, MemoryInitialization, Module, ModuleTranslation,
ModuleTranslation, PrimaryMap, SignatureIndex, StackMapInformation, Tunables, WasmFunctionInfo, PrimaryMap, SignatureIndex, StackMapInformation, Tunables, WasmFunctionInfo,
}; };
use wasmtime_runtime::{ use wasmtime_runtime::{
CompiledModuleId, CompiledModuleIdAllocator, GdbJitImageRegistration, InstantiationError, CompiledModuleId, CompiledModuleIdAllocator, GdbJitImageRegistration, MmapVec, VMFunctionBody,
MmapVec, VMFunctionBody, VMTrampoline, VMTrampoline,
}; };
/// An error condition while setting up a wasm instance, be it validation,
/// compilation, or instantiation.
#[derive(Error, Debug)]
pub enum SetupError {
/// The module did not pass validation.
#[error("Validation error: {0}")]
Validate(String),
/// A wasm translation error occurred.
#[error("WebAssembly failed to compile")]
Compile(#[from] CompileError),
/// Some runtime resource was unavailable or insufficient, or the start function
/// trapped.
#[error("Instantiation failed during setup")]
Instantiate(#[from] InstantiationError),
/// Debug information generation error occurred.
#[error("Debug information error")]
DebugInfo(#[from] anyhow::Error),
}
/// Secondary in-memory results of compilation. /// Secondary in-memory results of compilation.
/// ///
/// This opaque structure can be optionally passed back to /// This opaque structure can be optionally passed back to
@@ -446,7 +423,7 @@ impl CompiledModule {
if self.meta.native_debug_info_present { if self.meta.native_debug_info_present {
let text = self.text(); let text = self.text();
let bytes = create_gdbjit_image(self.mmap().to_vec(), (text.as_ptr(), text.len())) let bytes = create_gdbjit_image(self.mmap().to_vec(), (text.as_ptr(), text.len()))
.map_err(SetupError::DebugInfo)?; .context("failed to create jit image for gdb")?;
profiler.module_load(self, Some(&bytes)); profiler.module_load(self, Some(&bytes));
let reg = GdbJitImageRegistration::register(bytes); let reg = GdbJitImageRegistration::register(bytes);
self.dbg_jit_registration = Some(reg); self.dbg_jit_registration = Some(reg);

View File

@@ -29,7 +29,7 @@ mod unwind;
pub use crate::code_memory::CodeMemory; pub use crate::code_memory::CodeMemory;
pub use crate::instantiate::{ pub use crate::instantiate::{
subslice_range, CompiledModule, CompiledModuleInfo, ObjectBuilder, SetupError, SymbolizeContext, subslice_range, CompiledModule, CompiledModuleInfo, ObjectBuilder, SymbolizeContext,
}; };
pub use demangling::*; pub use demangling::*;
pub use profiling::*; pub use profiling::*;

View File

@@ -19,7 +19,6 @@ libc = { version = "0.2.112", default-features = false }
log = { workspace = true } log = { workspace = true }
memoffset = "0.6.0" memoffset = "0.6.0"
indexmap = "1.0.2" indexmap = "1.0.2"
thiserror = { workspace = true }
cfg-if = "1.0" cfg-if = "1.0"
rand = { version = "0.8.3", features = ['small_rng'] } rand = { version = "0.8.3", features = ['small_rng'] }
anyhow = { workspace = true } anyhow = { workspace = true }

View File

@@ -3,7 +3,6 @@
#![cfg_attr(not(unix), allow(unused_imports, unused_variables))] #![cfg_attr(not(unix), allow(unused_imports, unused_variables))]
use crate::InstantiationError;
use crate::MmapVec; use crate::MmapVec;
use anyhow::Result; use anyhow::Result;
use libc::c_void; use libc::c_void;
@@ -486,7 +485,7 @@ impl MemoryImageSlot {
initial_size_bytes: usize, initial_size_bytes: usize,
maybe_image: Option<&Arc<MemoryImage>>, maybe_image: Option<&Arc<MemoryImage>>,
style: &MemoryStyle, style: &MemoryStyle,
) -> Result<(), InstantiationError> { ) -> Result<()> {
assert!(!self.dirty); assert!(!self.dirty);
assert!(initial_size_bytes <= self.static_size); assert!(initial_size_bytes <= self.static_size);
@@ -499,16 +498,14 @@ impl MemoryImageSlot {
// extent of the prior initialization image in order to preserve // extent of the prior initialization image in order to preserve
// resident memory that might come before or after the image. // resident memory that might come before or after the image.
if self.image.as_ref() != maybe_image { if self.image.as_ref() != maybe_image {
self.remove_image() self.remove_image()?;
.map_err(|e| InstantiationError::Resource(e.into()))?;
} }
// The next order of business is to ensure that `self.accessible` is // The next order of business is to ensure that `self.accessible` is
// appropriate. First up is to grow the read/write portion of memory if // appropriate. First up is to grow the read/write portion of memory if
// it's not large enough to accommodate `initial_size_bytes`. // it's not large enough to accommodate `initial_size_bytes`.
if self.accessible < initial_size_bytes { if self.accessible < initial_size_bytes {
self.set_protection(self.accessible..initial_size_bytes, true) self.set_protection(self.accessible..initial_size_bytes, true)?;
.map_err(|e| InstantiationError::Resource(e.into()))?;
self.accessible = initial_size_bytes; self.accessible = initial_size_bytes;
} }
@@ -523,8 +520,7 @@ impl MemoryImageSlot {
if initial_size_bytes < self.accessible { if initial_size_bytes < self.accessible {
match style { match style {
MemoryStyle::Static { .. } => { MemoryStyle::Static { .. } => {
self.set_protection(initial_size_bytes..self.accessible, false) self.set_protection(initial_size_bytes..self.accessible, false)?;
.map_err(|e| InstantiationError::Resource(e.into()))?;
self.accessible = initial_size_bytes; self.accessible = initial_size_bytes;
} }
MemoryStyle::Dynamic { .. } => {} MemoryStyle::Dynamic { .. } => {}
@@ -543,9 +539,7 @@ impl MemoryImageSlot {
); );
if image.len > 0 { if image.len > 0 {
unsafe { unsafe {
image image.map_at(self.base)?;
.map_at(self.base)
.map_err(|e| InstantiationError::Resource(e.into()))?;
} }
} }
} }

View File

@@ -3,13 +3,12 @@ use crate::instance::{Instance, InstanceHandle, RuntimeMemoryCreator};
use crate::memory::{DefaultMemoryCreator, Memory}; use crate::memory::{DefaultMemoryCreator, Memory};
use crate::table::Table; use crate::table::Table;
use crate::{CompiledModuleId, ModuleRuntimeInfo, Store}; use crate::{CompiledModuleId, ModuleRuntimeInfo, Store};
use anyhow::Result; use anyhow::{anyhow, bail, Result};
use std::alloc; use std::alloc;
use std::any::Any; use std::any::Any;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error;
use wasmtime_environ::{ use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization, DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets, MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets,
@@ -86,46 +85,6 @@ impl StorePtr {
} }
} }
/// An link error while instantiating a module.
#[derive(Error, Debug)]
#[error("Link error: {0}")]
pub struct LinkError(pub String);
/// An error while instantiating a module.
#[derive(Error, Debug)]
pub enum InstantiationError {
/// Insufficient resources available for execution.
#[error("Insufficient resources: {0}")]
Resource(anyhow::Error),
/// A wasm link error occurred.
#[error("Failed to link module")]
Link(#[from] LinkError),
/// A trap ocurred during instantiation, after linking.
#[error("Trap occurred during instantiation")]
Trap(Trap),
/// A limit on how many instances are supported has been reached.
#[error("Limit of {0} concurrent instances has been reached")]
Limit(u32),
}
/// An error while creating a fiber stack.
#[cfg(feature = "async")]
#[derive(Error, Debug)]
pub enum FiberStackError {
/// Insufficient resources available for the request.
#[error("Insufficient resources: {0}")]
Resource(anyhow::Error),
/// An error for when the allocator doesn't support fiber stacks.
#[error("fiber stacks are not supported by the allocator")]
NotSupported,
/// A limit on how many fibers are supported has been reached.
#[error("Limit of {0} concurrent fibers has been reached")]
Limit(u32),
}
/// Represents a runtime instance allocator. /// Represents a runtime instance allocator.
/// ///
/// # Safety /// # Safety
@@ -151,10 +110,7 @@ pub unsafe trait InstanceAllocator: Send + Sync {
/// ///
/// This method is not inherently unsafe, but care must be made to ensure /// This method is not inherently unsafe, but care must be made to ensure
/// pointers passed in the allocation request outlive the returned instance. /// pointers passed in the allocation request outlive the returned instance.
unsafe fn allocate( unsafe fn allocate(&self, req: InstanceAllocationRequest) -> Result<InstanceHandle>;
&self,
req: InstanceAllocationRequest,
) -> Result<InstanceHandle, InstantiationError>;
/// Finishes the instantiation process started by an instance allocator. /// Finishes the instantiation process started by an instance allocator.
/// ///
@@ -166,7 +122,7 @@ pub unsafe trait InstanceAllocator: Send + Sync {
handle: &mut InstanceHandle, handle: &mut InstanceHandle,
module: &Module, module: &Module,
is_bulk_memory: bool, is_bulk_memory: bool,
) -> Result<(), InstantiationError>; ) -> Result<()>;
/// Deallocates a previously allocated instance. /// Deallocates a previously allocated instance.
/// ///
@@ -180,7 +136,7 @@ pub unsafe trait InstanceAllocator: Send + Sync {
/// Allocates a fiber stack for calling async functions on. /// Allocates a fiber stack for calling async functions on.
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack, FiberStackError>; fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack>;
/// Deallocates a fiber stack that was previously allocated with `allocate_fiber_stack`. /// Deallocates a fiber stack that was previously allocated with `allocate_fiber_stack`.
/// ///
@@ -198,10 +154,7 @@ pub unsafe trait InstanceAllocator: Send + Sync {
fn purge_module(&self, module: CompiledModuleId); fn purge_module(&self, module: CompiledModuleId);
} }
fn get_table_init_start( fn get_table_init_start(init: &TableInitializer, instance: &Instance) -> Result<u32> {
init: &TableInitializer,
instance: &Instance,
) -> Result<u32, InstantiationError> {
match init.base { match init.base {
Some(base) => { Some(base) => {
let val = unsafe { let val = unsafe {
@@ -212,20 +165,15 @@ fn get_table_init_start(
} }
}; };
init.offset.checked_add(val).ok_or_else(|| { init.offset
InstantiationError::Link(LinkError( .checked_add(val)
"element segment global base overflows".to_owned(), .ok_or_else(|| anyhow!("element segment global base overflows"))
))
})
} }
None => Ok(init.offset), None => Ok(init.offset),
} }
} }
fn check_table_init_bounds( fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
instance: &mut Instance,
module: &Module,
) -> Result<(), InstantiationError> {
match &module.table_initialization { match &module.table_initialization {
TableInitialization::FuncTable { segments, .. } TableInitialization::FuncTable { segments, .. }
| TableInitialization::Segments { segments } => { | TableInitialization::Segments { segments } => {
@@ -240,9 +188,7 @@ fn check_table_init_bounds(
// Initializer is in bounds // Initializer is in bounds
} }
_ => { _ => {
return Err(InstantiationError::Link(LinkError( bail!("table out of bounds: elements segment does not fit")
"table out of bounds: elements segment does not fit".to_owned(),
)))
} }
} }
} }
@@ -252,7 +198,7 @@ fn check_table_init_bounds(
Ok(()) Ok(())
} }
fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<(), InstantiationError> { fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
// Note: if the module's table initializer state is in // Note: if the module's table initializer state is in
// FuncTable mode, we will lazily initialize tables based on // FuncTable mode, we will lazily initialize tables based on
// any statically-precomputed image of FuncIndexes, but there // any statically-precomputed image of FuncIndexes, but there
@@ -264,15 +210,13 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<(), Ins
TableInitialization::FuncTable { segments, .. } TableInitialization::FuncTable { segments, .. }
| TableInitialization::Segments { segments } => { | TableInitialization::Segments { segments } => {
for segment in segments { for segment in segments {
instance instance.table_init_segment(
.table_init_segment( segment.table_index,
segment.table_index, &segment.elements,
&segment.elements, get_table_init_start(segment, instance)?,
get_table_init_start(segment, instance)?, 0,
0, segment.elements.len() as u32,
segment.elements.len() as u32, )?;
)
.map_err(InstantiationError::Trap)?;
} }
} }
} }
@@ -280,10 +224,7 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<(), Ins
Ok(()) Ok(())
} }
fn get_memory_init_start( fn get_memory_init_start(init: &MemoryInitializer, instance: &Instance) -> Result<u64> {
init: &MemoryInitializer,
instance: &Instance,
) -> Result<u64, InstantiationError> {
match init.base { match init.base {
Some(base) => { Some(base) => {
let mem64 = instance.module().memory_plans[init.memory_index] let mem64 = instance.module().memory_plans[init.memory_index]
@@ -302,18 +243,15 @@ fn get_memory_init_start(
} }
}; };
init.offset.checked_add(val).ok_or_else(|| { init.offset
InstantiationError::Link(LinkError("data segment global base overflows".to_owned())) .checked_add(val)
}) .ok_or_else(|| anyhow!("data segment global base overflows"))
} }
None => Ok(init.offset), None => Ok(init.offset),
} }
} }
fn check_memory_init_bounds( fn check_memory_init_bounds(instance: &Instance, initializers: &[MemoryInitializer]) -> Result<()> {
instance: &Instance,
initializers: &[MemoryInitializer],
) -> Result<(), InstantiationError> {
for init in initializers { for init in initializers {
let memory = instance.get_memory(init.memory_index); let memory = instance.get_memory(init.memory_index);
let start = get_memory_init_start(init, instance)?; let start = get_memory_init_start(init, instance)?;
@@ -326,9 +264,7 @@ fn check_memory_init_bounds(
// Initializer is in bounds // Initializer is in bounds
} }
_ => { _ => {
return Err(InstantiationError::Link(LinkError( bail!("memory out of bounds: data segment does not fit")
"memory out of bounds: data segment does not fit".into(),
)))
} }
} }
} }
@@ -336,7 +272,7 @@ fn check_memory_init_bounds(
Ok(()) Ok(())
} }
fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<(), InstantiationError> { fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<()> {
let memory_size_in_pages = let memory_size_in_pages =
&|memory| (instance.get_memory(memory).current_length() as u64) / u64::from(WASM_PAGE_SIZE); &|memory| (instance.get_memory(memory).current_length() as u64) / u64::from(WASM_PAGE_SIZE);
@@ -392,13 +328,13 @@ fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<(), I
}, },
); );
if !ok { if !ok {
return Err(InstantiationError::Trap(Trap::MemoryOutOfBounds)); return Err(Trap::MemoryOutOfBounds.into());
} }
Ok(()) Ok(())
} }
fn check_init_bounds(instance: &mut Instance, module: &Module) -> Result<(), InstantiationError> { fn check_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
check_table_init_bounds(instance, module)?; check_table_init_bounds(instance, module)?;
match &instance.module().memory_initialization { match &instance.module().memory_initialization {
@@ -416,7 +352,7 @@ fn initialize_instance(
instance: &mut Instance, instance: &mut Instance,
module: &Module, module: &Module,
is_bulk_memory: bool, is_bulk_memory: bool,
) -> Result<(), InstantiationError> { ) -> Result<()> {
// If bulk memory is not enabled, bounds check the data and element segments before // If bulk memory is not enabled, bounds check the data and element segments before
// making any changes. With bulk memory enabled, initializers are processed // making any changes. With bulk memory enabled, initializers are processed
// in-order and side effects are observed up to the point of an out-of-bounds // in-order and side effects are observed up to the point of an out-of-bounds
@@ -456,20 +392,17 @@ impl OnDemandInstanceAllocator {
fn create_tables( fn create_tables(
store: &mut StorePtr, store: &mut StorePtr,
runtime_info: &Arc<dyn ModuleRuntimeInfo>, runtime_info: &Arc<dyn ModuleRuntimeInfo>,
) -> Result<PrimaryMap<DefinedTableIndex, Table>, InstantiationError> { ) -> Result<PrimaryMap<DefinedTableIndex, Table>> {
let module = runtime_info.module(); let module = runtime_info.module();
let num_imports = module.num_imported_tables; let num_imports = module.num_imported_tables;
let mut tables: PrimaryMap<DefinedTableIndex, _> = let mut tables: PrimaryMap<DefinedTableIndex, _> =
PrimaryMap::with_capacity(module.table_plans.len() - num_imports); PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
for (_, table) in module.table_plans.iter().skip(num_imports) { for (_, table) in module.table_plans.iter().skip(num_imports) {
tables.push( tables.push(Table::new_dynamic(table, unsafe {
Table::new_dynamic(table, unsafe { store
store .get()
.get() .expect("if module has table plans, store is not empty")
.expect("if module has table plans, store is not empty") })?);
})
.map_err(InstantiationError::Resource)?,
);
} }
Ok(tables) Ok(tables)
} }
@@ -478,7 +411,7 @@ impl OnDemandInstanceAllocator {
&self, &self,
store: &mut StorePtr, store: &mut StorePtr,
runtime_info: &Arc<dyn ModuleRuntimeInfo>, runtime_info: &Arc<dyn ModuleRuntimeInfo>,
) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>, InstantiationError> { ) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>> {
let module = runtime_info.module(); let module = runtime_info.module();
let creator = self let creator = self
.mem_creator .mem_creator
@@ -491,23 +424,18 @@ impl OnDemandInstanceAllocator {
let defined_memory_idx = module let defined_memory_idx = module
.defined_memory_index(memory_idx) .defined_memory_index(memory_idx)
.expect("Skipped imports, should never be None"); .expect("Skipped imports, should never be None");
let image = runtime_info let image = runtime_info.memory_image(defined_memory_idx)?;
.memory_image(defined_memory_idx)
.map_err(|err| InstantiationError::Resource(err.into()))?;
memories.push( memories.push(Memory::new_dynamic(
Memory::new_dynamic( plan,
plan, creator,
creator, unsafe {
unsafe { store
store .get()
.get() .expect("if module has memory plans, store is not empty")
.expect("if module has memory plans, store is not empty") },
}, image,
image, )?);
)
.map_err(InstantiationError::Resource)?,
);
} }
Ok(memories) Ok(memories)
} }
@@ -532,7 +460,7 @@ impl Default for OnDemandInstanceAllocator {
pub unsafe fn allocate_single_memory_instance( pub unsafe fn allocate_single_memory_instance(
req: InstanceAllocationRequest, req: InstanceAllocationRequest,
memory: Memory, memory: Memory,
) -> Result<InstanceHandle, InstantiationError> { ) -> Result<InstanceHandle> {
let mut memories = PrimaryMap::default(); let mut memories = PrimaryMap::default();
memories.push(memory); memories.push(memory);
let tables = PrimaryMap::default(); let tables = PrimaryMap::default();
@@ -554,10 +482,7 @@ pub unsafe fn deallocate(handle: &InstanceHandle) {
} }
unsafe impl InstanceAllocator for OnDemandInstanceAllocator { unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
unsafe fn allocate( unsafe fn allocate(&self, mut req: InstanceAllocationRequest) -> Result<InstanceHandle> {
&self,
mut req: InstanceAllocationRequest,
) -> Result<InstanceHandle, InstantiationError> {
let memories = self.create_memories(&mut req.store, &req.runtime_info)?; let memories = self.create_memories(&mut req.store, &req.runtime_info)?;
let tables = Self::create_tables(&mut req.store, &req.runtime_info)?; let tables = Self::create_tables(&mut req.store, &req.runtime_info)?;
let module = req.runtime_info.module(); let module = req.runtime_info.module();
@@ -577,7 +502,7 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
handle: &mut InstanceHandle, handle: &mut InstanceHandle,
module: &Module, module: &Module,
is_bulk_memory: bool, is_bulk_memory: bool,
) -> Result<(), InstantiationError> { ) -> Result<()> {
initialize_instance(handle.instance_mut(), module, is_bulk_memory) initialize_instance(handle.instance_mut(), module, is_bulk_memory)
} }
@@ -586,13 +511,13 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack, FiberStackError> { fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
if self.stack_size == 0 { if self.stack_size == 0 {
return Err(FiberStackError::NotSupported); bail!("fiber stacks are not supported by the allocator")
} }
wasmtime_fiber::FiberStack::new(self.stack_size) let stack = wasmtime_fiber::FiberStack::new(self.stack_size)?;
.map_err(|e| FiberStackError::Resource(e.into())) Ok(stack)
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]

View File

@@ -7,10 +7,7 @@
//! Using the pooling instance allocator can speed up module instantiation //! Using the pooling instance allocator can speed up module instantiation
//! when modules can be constrained based on configurable limits. //! when modules can be constrained based on configurable limits.
use super::{ use super::{initialize_instance, InstanceAllocationRequest, InstanceAllocator, InstanceHandle};
initialize_instance, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
InstantiationError,
};
use crate::{instance::Instance, Memory, Mmap, Table}; use crate::{instance::Instance, Memory, Mmap, Table};
use crate::{CompiledModuleId, MemoryImageSlot, ModuleRuntimeInfo, Store}; use crate::{CompiledModuleId, MemoryImageSlot, ModuleRuntimeInfo, Store};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
@@ -41,9 +38,6 @@ use imp::{commit_table_pages, decommit_table_pages};
#[cfg(all(feature = "async", unix))] #[cfg(all(feature = "async", unix))]
use imp::{commit_stack_pages, reset_stack_pages_to_zero}; use imp::{commit_stack_pages, reset_stack_pages_to_zero};
#[cfg(feature = "async")]
use super::FiberStackError;
fn round_up_to_pow2(n: usize, to: usize) -> usize { fn round_up_to_pow2(n: usize, to: usize) -> usize {
debug_assert!(to > 0); debug_assert!(to > 0);
debug_assert!(to.is_power_of_two()); debug_assert!(to.is_power_of_two());
@@ -167,7 +161,7 @@ impl InstancePool {
&self, &self,
instance_index: usize, instance_index: usize,
req: InstanceAllocationRequest, req: InstanceAllocationRequest,
) -> Result<InstanceHandle, InstantiationError> { ) -> Result<InstanceHandle> {
let module = req.runtime_info.module(); let module = req.runtime_info.module();
// Before doing anything else ensure that our instance slot is actually // Before doing anything else ensure that our instance slot is actually
@@ -175,9 +169,7 @@ impl InstancePool {
// If this fails then it's a configuration error at the `Engine` level // If this fails then it's a configuration error at the `Engine` level
// from when this pooling allocator was created and that needs updating // from when this pooling allocator was created and that needs updating
// if this is to succeed. // if this is to succeed.
let offsets = self let offsets = self.validate_instance_size(module)?;
.validate_instance_size(module)
.map_err(InstantiationError::Resource)?;
let mut memories = let mut memories =
PrimaryMap::with_capacity(module.memory_plans.len() - module.num_imported_memories); PrimaryMap::with_capacity(module.memory_plans.len() - module.num_imported_memories);
@@ -214,14 +206,16 @@ impl InstancePool {
}) })
} }
fn allocate( fn allocate(&self, req: InstanceAllocationRequest) -> Result<InstanceHandle> {
&self,
req: InstanceAllocationRequest,
) -> Result<InstanceHandle, InstantiationError> {
let id = self let id = self
.index_allocator .index_allocator
.alloc(req.runtime_info.unique_id()) .alloc(req.runtime_info.unique_id())
.ok_or_else(|| InstantiationError::Limit(self.max_instances as u32))?; .ok_or_else(|| {
anyhow!(
"maximum concurrent instance limit of {} reached",
self.max_instances
)
})?;
match unsafe { self.initialize_instance(id.index(), req) } { match unsafe { self.initialize_instance(id.index(), req) } {
Ok(handle) => Ok(handle), Ok(handle) => Ok(handle),
@@ -271,7 +265,7 @@ impl InstancePool {
store: Option<*mut dyn Store>, store: Option<*mut dyn Store>,
memories: &mut PrimaryMap<DefinedMemoryIndex, Memory>, memories: &mut PrimaryMap<DefinedMemoryIndex, Memory>,
tables: &mut PrimaryMap<DefinedTableIndex, Table>, tables: &mut PrimaryMap<DefinedTableIndex, Table>,
) -> Result<(), InstantiationError> { ) -> Result<()> {
self.allocate_memories(instance_index, runtime_info, store, memories)?; self.allocate_memories(instance_index, runtime_info, store, memories)?;
self.allocate_tables(instance_index, runtime_info, store, tables)?; self.allocate_tables(instance_index, runtime_info, store, tables)?;
@@ -284,11 +278,10 @@ impl InstancePool {
runtime_info: &dyn ModuleRuntimeInfo, runtime_info: &dyn ModuleRuntimeInfo,
store: Option<*mut dyn Store>, store: Option<*mut dyn Store>,
memories: &mut PrimaryMap<DefinedMemoryIndex, Memory>, memories: &mut PrimaryMap<DefinedMemoryIndex, Memory>,
) -> Result<(), InstantiationError> { ) -> Result<()> {
let module = runtime_info.module(); let module = runtime_info.module();
self.validate_memory_plans(module) self.validate_memory_plans(module)?;
.map_err(InstantiationError::Resource)?;
for (memory_index, plan) in module for (memory_index, plan) in module
.memory_plans .memory_plans
@@ -321,9 +314,7 @@ impl InstancePool {
let mut slot = self let mut slot = self
.memories .memories
.take_memory_image_slot(instance_index, defined_index); .take_memory_image_slot(instance_index, defined_index);
let image = runtime_info let image = runtime_info.memory_image(defined_index)?;
.memory_image(defined_index)
.map_err(|err| InstantiationError::Resource(err.into()))?;
let initial_size = plan.memory.minimum * WASM_PAGE_SIZE as u64; let initial_size = plan.memory.minimum * WASM_PAGE_SIZE as u64;
// If instantiation fails, we can propagate the error // If instantiation fails, we can propagate the error
@@ -339,13 +330,11 @@ impl InstancePool {
// the process to continue, because we never perform a // the process to continue, because we never perform a
// mmap that would leave an open space for someone // mmap that would leave an open space for someone
// else to come in and map something. // else to come in and map something.
slot.instantiate(initial_size as usize, image, &plan.style) slot.instantiate(initial_size as usize, image, &plan.style)?;
.map_err(|e| InstantiationError::Resource(e.into()))?;
memories.push( memories.push(Memory::new_static(plan, memory, slot, unsafe {
Memory::new_static(plan, memory, slot, unsafe { &mut *store.unwrap() }) &mut *store.unwrap()
.map_err(InstantiationError::Resource)?, })?);
);
} }
Ok(()) Ok(())
@@ -380,11 +369,10 @@ impl InstancePool {
runtime_info: &dyn ModuleRuntimeInfo, runtime_info: &dyn ModuleRuntimeInfo,
store: Option<*mut dyn Store>, store: Option<*mut dyn Store>,
tables: &mut PrimaryMap<DefinedTableIndex, Table>, tables: &mut PrimaryMap<DefinedTableIndex, Table>,
) -> Result<(), InstantiationError> { ) -> Result<()> {
let module = runtime_info.module(); let module = runtime_info.module();
self.validate_table_plans(module) self.validate_table_plans(module)?;
.map_err(InstantiationError::Resource)?;
let mut bases = self.tables.get(instance_index); let mut bases = self.tables.get(instance_index);
for (_, plan) in module.table_plans.iter().skip(module.num_imported_tables) { for (_, plan) in module.table_plans.iter().skip(module.num_imported_tables) {
@@ -393,19 +381,13 @@ impl InstancePool {
commit_table_pages( commit_table_pages(
base as *mut u8, base as *mut u8,
self.tables.max_elements as usize * mem::size_of::<*mut u8>(), self.tables.max_elements as usize * mem::size_of::<*mut u8>(),
) )?;
.map_err(InstantiationError::Resource)?;
tables.push( tables.push(Table::new_static(
Table::new_static( plan,
plan, unsafe { std::slice::from_raw_parts_mut(base, self.tables.max_elements as usize) },
unsafe { unsafe { &mut *store.unwrap() },
std::slice::from_raw_parts_mut(base, self.tables.max_elements as usize) )?);
},
unsafe { &mut *store.unwrap() },
)
.map_err(InstantiationError::Resource)?,
);
} }
Ok(()) Ok(())
@@ -930,15 +912,20 @@ impl StackPool {
}) })
} }
fn allocate(&self) -> Result<wasmtime_fiber::FiberStack, FiberStackError> { fn allocate(&self) -> Result<wasmtime_fiber::FiberStack> {
if self.stack_size == 0 { if self.stack_size == 0 {
return Err(FiberStackError::NotSupported); bail!("pooling allocator not configured to enable fiber stack allocation");
} }
let index = self let index = self
.index_allocator .index_allocator
.alloc(None) .alloc(None)
.ok_or(FiberStackError::Limit(self.max_instances as u32))? .ok_or_else(|| {
anyhow!(
"maximum concurrent fiber limit of {} reached",
self.max_instances
)
})?
.index(); .index();
assert!(index < self.max_instances); assert!(index < self.max_instances);
@@ -952,11 +939,11 @@ impl StackPool {
.as_mut_ptr() .as_mut_ptr()
.add((index * self.stack_size) + self.page_size); .add((index * self.stack_size) + self.page_size);
commit_stack_pages(bottom_of_stack, size_without_guard) commit_stack_pages(bottom_of_stack, size_without_guard)?;
.map_err(FiberStackError::Resource)?;
wasmtime_fiber::FiberStack::from_top_ptr(bottom_of_stack.add(size_without_guard)) let stack =
.map_err(|e| FiberStackError::Resource(e.into())) wasmtime_fiber::FiberStack::from_top_ptr(bottom_of_stack.add(size_without_guard))?;
Ok(stack)
} }
} }
@@ -1106,10 +1093,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
Ok(()) Ok(())
} }
unsafe fn allocate( unsafe fn allocate(&self, req: InstanceAllocationRequest) -> Result<InstanceHandle> {
&self,
req: InstanceAllocationRequest,
) -> Result<InstanceHandle, InstantiationError> {
self.instances.allocate(req) self.instances.allocate(req)
} }
@@ -1118,7 +1102,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
handle: &mut InstanceHandle, handle: &mut InstanceHandle,
module: &Module, module: &Module,
is_bulk_memory: bool, is_bulk_memory: bool,
) -> Result<(), InstantiationError> { ) -> Result<()> {
let instance = handle.instance_mut(); let instance = handle.instance_mut();
initialize_instance(instance, module, is_bulk_memory) initialize_instance(instance, module, is_bulk_memory)
} }
@@ -1128,7 +1112,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
} }
#[cfg(all(feature = "async", unix))] #[cfg(all(feature = "async", unix))]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack, FiberStackError> { fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
self.stacks.allocate() self.stacks.allocate()
} }
@@ -1138,14 +1122,14 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
} }
#[cfg(all(feature = "async", windows))] #[cfg(all(feature = "async", windows))]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack, FiberStackError> { fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
if self.stack_size == 0 { if self.stack_size == 0 {
return Err(FiberStackError::NotSupported); bail!("fiber stack allocation not supported")
} }
// On windows, we don't use a stack pool as we use the native fiber implementation // On windows, we don't use a stack pool as we use the native fiber implementation
wasmtime_fiber::FiberStack::new(self.stack_size) let stack = wasmtime_fiber::FiberStack::new(self.stack_size)?;
.map_err(|e| FiberStackError::Resource(e.into())) Ok(stack)
} }
#[cfg(all(feature = "async", windows))] #[cfg(all(feature = "async", windows))]
@@ -1269,7 +1253,7 @@ mod test {
host_state: Box::new(()), host_state: Box::new(()),
store: StorePtr::empty(), store: StorePtr::empty(),
}) { }) {
Err(InstantiationError::Limit(3)) => {} Err(_) => {}
_ => panic!("unexpected error"), _ => panic!("unexpected error"),
}; };
@@ -1414,10 +1398,7 @@ mod test {
assert_eq!(pool.index_allocator.testing_freelist(), []); assert_eq!(pool.index_allocator.testing_freelist(), []);
match pool.allocate().unwrap_err() { pool.allocate().unwrap_err();
FiberStackError::Limit(10) => {}
_ => panic!("unexpected error"),
};
for stack in stacks { for stack in stacks {
pool.deallocate(&stack); pool.deallocate(&stack);

View File

@@ -55,7 +55,7 @@ pub use crate::externref::*;
pub use crate::imports::Imports; pub use crate::imports::Imports;
pub use crate::instance::{ pub use crate::instance::{
allocate_single_memory_instance, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, allocate_single_memory_instance, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
InstantiationError, LinkError, OnDemandInstanceAllocator, StorePtr, OnDemandInstanceAllocator, StorePtr,
}; };
#[cfg(feature = "pooling-allocator")] #[cfg(feature = "pooling-allocator")]
pub use crate::instance::{ pub use crate::instance::{

View File

@@ -5,13 +5,13 @@ use crate::{
AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, SharedMemory, AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, SharedMemory,
StoreContextMut, Table, TypedFunc, StoreContextMut, Table, TypedFunc,
}; };
use anyhow::{anyhow, bail, Context, Error, Result}; use anyhow::{anyhow, bail, Context, Result};
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
use wasmtime_environ::{EntityType, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, TableIndex}; use wasmtime_environ::{EntityType, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, TableIndex};
use wasmtime_runtime::{ use wasmtime_runtime::{
Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody, Imports, InstanceAllocationRequest, StorePtr, VMContext, VMFunctionBody, VMFunctionImport,
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMOpaqueContext, VMTableImport, VMGlobalImport, VMMemoryImport, VMOpaqueContext, VMTableImport,
}; };
/// An instantiated WebAssembly module. /// An instantiated WebAssembly module.
@@ -317,20 +317,11 @@ impl Instance {
// items from this instance into other instances should be ok when // items from this instance into other instances should be ok when
// those items are loaded and run we'll have all the metadata to // those items are loaded and run we'll have all the metadata to
// look at them. // look at them.
store store.engine().allocator().initialize(
.engine() &mut instance_handle,
.allocator() compiled_module.module(),
.initialize( store.engine().config().features.bulk_memory,
&mut instance_handle, )?;
compiled_module.module(),
store.engine().config().features.bulk_memory,
)
.map_err(|e| -> Error {
match e {
InstantiationError::Trap(trap) => trap.into(),
other => other.into(),
}
})?;
Ok((instance, compiled_module.module().start_func)) Ok((instance, compiled_module.module().start_func))
} }

View File

@@ -8,8 +8,8 @@ use std::sync::Arc;
use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, WASM_PAGE_SIZE}; use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, WASM_PAGE_SIZE};
use wasmtime_runtime::{ use wasmtime_runtime::{
allocate_single_memory_instance, DefaultMemoryCreator, Imports, InstanceAllocationRequest, allocate_single_memory_instance, DefaultMemoryCreator, Imports, InstanceAllocationRequest,
InstantiationError, Memory, MemoryImage, RuntimeLinearMemory, RuntimeMemoryCreator, Memory, MemoryImage, RuntimeLinearMemory, RuntimeMemoryCreator, SharedMemory, StorePtr,
SharedMemory, StorePtr, VMMemoryDefinition, VMMemoryDefinition,
}; };
/// Create a "frankenstein" instance with a single memory. /// Create a "frankenstein" instance with a single memory.
@@ -48,8 +48,7 @@ pub fn create_memory(
.as_mut() .as_mut()
.expect("the store pointer cannot be null here") .expect("the store pointer cannot be null here")
}; };
Memory::new_dynamic(&plan, creator, store, None) Memory::new_dynamic(&plan, creator, store, None)?
.map_err(|err| InstantiationError::Resource(err.into()))?
} }
}; };

View File

@@ -253,7 +253,7 @@ fn test_initial_memory_limits_exceeded() -> Result<()> {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
"Insufficient resources: memory minimum size of 11 pages exceeds memory limits" "memory minimum size of 11 pages exceeds memory limits"
), ),
} }
@@ -261,7 +261,7 @@ fn test_initial_memory_limits_exceeded() -> Result<()> {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
"Insufficient resources: memory minimum size of 25 pages exceeds memory limits" "memory minimum size of 25 pages exceeds memory limits"
), ),
} }
@@ -329,7 +329,7 @@ fn test_initial_table_limits_exceeded() -> Result<()> {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
"Insufficient resources: table minimum size of 23 elements exceeds table limits" "table minimum size of 23 elements exceeds table limits"
), ),
} }
@@ -341,7 +341,7 @@ fn test_initial_table_limits_exceeded() -> Result<()> {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
"Insufficient resources: table minimum size of 99 elements exceeds table limits" "table minimum size of 99 elements exceeds table limits"
), ),
} }
@@ -374,7 +374,7 @@ fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
"Insufficient resources: memory minimum size of 5 pages exceeds memory limits" "memory minimum size of 5 pages exceeds memory limits"
), ),
} }

View File

@@ -428,7 +428,7 @@ fn instantiation_limit() -> Result<()> {
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
format!( format!(
"Limit of {} concurrent instances has been reached", "maximum concurrent instance limit of {} reached",
INSTANCE_LIMIT INSTANCE_LIMIT
) )
), ),