Merge pull request #2518 from peterhuene/add-allocator

Implement the pooling instance allocator.
This commit is contained in:
Peter Huene
2021-03-08 12:20:31 -08:00
committed by GitHub
51 changed files with 5330 additions and 1043 deletions

View File

@@ -73,3 +73,6 @@ experimental_x64 = ["wasmtime-jit/experimental_x64"]
# Enables support for "async stores" as well as defining host functions as
# `async fn` and calling functions asynchronously.
async = ["wasmtime-fiber"]
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
uffd = ["wasmtime-runtime/uffd"]

View File

@@ -14,6 +14,257 @@ use wasmtime_environ::settings::{self, Configurable, SetError};
use wasmtime_environ::{isa, isa::TargetIsa, Tunables};
use wasmtime_jit::{native, CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::{InstanceAllocator, OnDemandInstanceAllocator, PoolingInstanceAllocator};
/// Represents the limits placed on a module for compiling with the pooling instance allocation strategy.
#[derive(Debug, Copy, Clone)]
pub struct ModuleLimits {
/// The maximum number of imported functions for a module (default is 1000).
pub imported_functions: u32,
/// The maximum number of imported tables for a module (default is 0).
pub imported_tables: u32,
/// The maximum number of imported linear memories for a module (default is 0).
pub imported_memories: u32,
/// The maximum number of imported globals for a module (default is 0).
pub imported_globals: u32,
/// The maximum number of defined types for a module (default is 100).
pub types: u32,
/// The maximum number of defined functions for a module (default is 10000).
pub functions: u32,
/// The maximum number of defined tables for a module (default is 1).
pub tables: u32,
/// The maximum number of defined linear memories for a module (default is 1).
pub memories: u32,
/// The maximum number of defined globals for a module (default is 10).
pub globals: u32,
/// The maximum table elements for any table defined in a module (default is 10000).
///
/// If a table's minimum element limit is greater than this value, the module will
/// fail to compile.
///
/// If a table's maximum element limit is unbounded or greater than this value,
/// the maximum will be `table_elements` for the purpose of any `table.grow` instruction.
pub table_elements: u32,
/// The maximum number of pages for any linear memory defined in a module (default is 160).
///
/// The default of 160 means at most 10 MiB of host memory may be committed for each instance.
///
/// If a memory's minimum page limit is greater than this value, the module will
/// fail to compile.
///
/// If a memory's maximum page limit is unbounded or greater than this value,
/// the maximum will be `memory_pages` for the purpose of any `memory.grow` instruction.
///
/// This value cannot exceed any memory reservation size limits placed on instances.
pub memory_pages: u32,
}
impl Default for ModuleLimits {
fn default() -> Self {
// Use the defaults from the runtime
let wasmtime_runtime::ModuleLimits {
imported_functions,
imported_tables,
imported_memories,
imported_globals,
types,
functions,
tables,
memories,
globals,
table_elements,
memory_pages,
} = wasmtime_runtime::ModuleLimits::default();
Self {
imported_functions,
imported_tables,
imported_memories,
imported_globals,
types,
functions,
tables,
memories,
globals,
table_elements,
memory_pages,
}
}
}
// This exists so we can convert between the public Wasmtime API and the runtime representation
// without having to export runtime types from the Wasmtime API.
#[doc(hidden)]
impl Into<wasmtime_runtime::ModuleLimits> for ModuleLimits {
fn into(self) -> wasmtime_runtime::ModuleLimits {
let Self {
imported_functions,
imported_tables,
imported_memories,
imported_globals,
types,
functions,
tables,
memories,
globals,
table_elements,
memory_pages,
} = self;
wasmtime_runtime::ModuleLimits {
imported_functions,
imported_tables,
imported_memories,
imported_globals,
types,
functions,
tables,
memories,
globals,
table_elements,
memory_pages,
}
}
}
/// Represents the limits placed on instances by the pooling instance allocation strategy.
#[derive(Debug, Copy, Clone)]
pub struct InstanceLimits {
/// The maximum number of concurrent instances supported (default is 1000).
pub count: u32,
/// The maximum size, in bytes, of host address space to reserve for each linear memory of an instance.
///
/// Note: this value has important performance ramifications.
///
/// On 64-bit platforms, the default for this value will be 6 GiB. A value of less than 4 GiB will
/// force runtime bounds checking for memory accesses and thus will negatively impact performance.
/// Any value above 4 GiB will start eliding bounds checks provided the `offset` of the memory access is
/// less than (`memory_reservation_size` - 4 GiB). A value of 8 GiB will completely elide *all* bounds
/// checks; consequently, 8 GiB will be the maximum supported value. The default of 6 GiB reserves
/// less host address space for each instance, but a memory access with an offset above 2 GiB will incur
/// runtime bounds checks.
///
/// On 32-bit platforms, the default for this value will be 10 MiB. A 32-bit host has very limited address
/// space to reserve for a lot of concurrent instances. As a result, runtime bounds checking will be used
/// for all memory accesses. For better runtime performance, a 64-bit host is recommended.
///
/// This value will be rounded up by the WebAssembly page size (64 KiB).
pub memory_reservation_size: u64,
}
impl Default for InstanceLimits {
fn default() -> Self {
let wasmtime_runtime::InstanceLimits {
count,
memory_reservation_size,
} = wasmtime_runtime::InstanceLimits::default();
Self {
count,
memory_reservation_size,
}
}
}
// This exists so we can convert between the public Wasmtime API and the runtime representation
// without having to export runtime types from the Wasmtime API.
#[doc(hidden)]
impl Into<wasmtime_runtime::InstanceLimits> for InstanceLimits {
fn into(self) -> wasmtime_runtime::InstanceLimits {
let Self {
count,
memory_reservation_size,
} = self;
wasmtime_runtime::InstanceLimits {
count,
memory_reservation_size,
}
}
}
/// The allocation strategy to use for the pooling instance allocation strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PoolingAllocationStrategy {
/// Allocate from the next available instance.
NextAvailable,
/// Allocate from a random available instance.
Random,
}
impl Default for PoolingAllocationStrategy {
fn default() -> Self {
match wasmtime_runtime::PoolingAllocationStrategy::default() {
wasmtime_runtime::PoolingAllocationStrategy::NextAvailable => Self::NextAvailable,
wasmtime_runtime::PoolingAllocationStrategy::Random => Self::Random,
}
}
}
// This exists so we can convert between the public Wasmtime API and the runtime representation
// without having to export runtime types from the Wasmtime API.
#[doc(hidden)]
impl Into<wasmtime_runtime::PoolingAllocationStrategy> for PoolingAllocationStrategy {
fn into(self) -> wasmtime_runtime::PoolingAllocationStrategy {
match self {
Self::NextAvailable => wasmtime_runtime::PoolingAllocationStrategy::NextAvailable,
Self::Random => wasmtime_runtime::PoolingAllocationStrategy::Random,
}
}
}
/// Represents the module instance allocation strategy to use.
#[derive(Clone)]
pub enum InstanceAllocationStrategy {
/// The on-demand instance allocation strategy.
///
/// Resources related to a module instance are allocated at instantiation time and
/// immediately deallocated when the `Store` referencing the instance is dropped.
///
/// This is the default allocation strategy for Wasmtime.
OnDemand,
/// The pooling instance allocation strategy.
///
/// A pool of resources is created in advance and module instantiation reuses resources
/// from the pool. Resources are returned to the pool when the `Store` referencing the instance
/// is dropped.
Pooling {
/// The allocation strategy to use.
strategy: PoolingAllocationStrategy,
/// The module limits to use.
module_limits: ModuleLimits,
/// The instance limits to use.
instance_limits: InstanceLimits,
},
}
impl InstanceAllocationStrategy {
/// The default pooling instance allocation strategy.
pub fn pooling() -> Self {
Self::Pooling {
strategy: PoolingAllocationStrategy::default(),
module_limits: ModuleLimits::default(),
instance_limits: InstanceLimits::default(),
}
}
}
impl Default for InstanceAllocationStrategy {
fn default() -> Self {
Self::OnDemand
}
}
/// Global configuration options used to create an [`Engine`](crate::Engine)
/// and customize its behavior.
@@ -29,13 +280,18 @@ pub struct Config {
#[cfg(feature = "cache")]
pub(crate) cache_config: CacheConfig,
pub(crate) profiler: Arc<dyn ProfilingAgent>,
pub(crate) memory_creator: Option<MemoryCreatorProxy>,
pub(crate) instance_allocator: Option<Arc<dyn InstanceAllocator>>,
// The default instance allocator is used for instantiating host objects
// and for module instantiation when `instance_allocator` is None
pub(crate) default_instance_allocator: OnDemandInstanceAllocator,
pub(crate) max_wasm_stack: usize,
pub(crate) features: WasmFeatures,
pub(crate) wasm_backtrace_details_env_used: bool,
pub(crate) max_instances: usize,
pub(crate) max_tables: usize,
pub(crate) max_memories: usize,
#[cfg(feature = "async")]
pub(crate) async_stack_size: usize,
}
impl Config {
@@ -73,7 +329,8 @@ impl Config {
#[cfg(feature = "cache")]
cache_config: CacheConfig::new_cache_disabled(),
profiler: Arc::new(NullProfilerAgent),
memory_creator: None,
instance_allocator: None,
default_instance_allocator: OnDemandInstanceAllocator::new(None),
max_wasm_stack: 1 << 20,
wasm_backtrace_details_env_used: false,
features: WasmFeatures {
@@ -85,6 +342,8 @@ impl Config {
max_instances: 10_000,
max_tables: 10_000,
max_memories: 10_000,
#[cfg(feature = "async")]
async_stack_size: 2 << 20,
};
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
return ret;
@@ -159,23 +418,75 @@ impl Config {
self
}
/// Configures the maximum amount of native stack space available to
/// Configures the maximum amount of stack space available for
/// executing WebAssembly code.
///
/// WebAssembly code currently executes on the native call stack for its own
/// call frames. WebAssembly, however, also has well-defined semantics on
/// stack overflow. This is intended to be a knob which can help configure
/// how much native stack space a wasm module is allowed to consume. Note
/// that the number here is not super-precise, but rather wasm will take at
/// most "pretty close to this much" stack space.
/// WebAssembly has well-defined semantics on stack overflow. This is
/// intended to be a knob which can help configure how much stack space
/// wasm execution is allowed to consume. Note that the number here is not
/// super-precise, but rather wasm will take at most "pretty close to this
/// much" stack space.
///
/// If a wasm call (or series of nested wasm calls) take more stack space
/// than the `size` specified then a stack overflow trap will be raised.
///
/// By default this option is 1 MB.
pub fn max_wasm_stack(&mut self, size: usize) -> &mut Self {
/// When the `async` feature is enabled, this value cannot exceed the
/// `async_stack_size` option. Be careful not to set this value too close
/// to `async_stack_size` as doing so may limit how much stack space
/// is available for host functions. Unlike wasm functions that trap
/// on stack overflow, a host function that overflows the stack will
/// abort the process.
///
/// `max_wasm_stack` must be set prior to setting an instance allocation
/// strategy.
///
/// By default this option is 1 MiB.
pub fn max_wasm_stack(&mut self, size: usize) -> Result<&mut Self> {
#[cfg(feature = "async")]
if size > self.async_stack_size {
bail!("wasm stack size cannot exceed the async stack size");
}
if size == 0 {
bail!("wasm stack size cannot be zero");
}
if self.instance_allocator.is_some() {
bail!(
"wasm stack size cannot be modified after setting an instance allocation strategy"
);
}
self.max_wasm_stack = size;
self
Ok(self)
}
/// Configures the size of the stacks used for asynchronous execution.
///
/// This setting configures the size of the stacks that are allocated for
/// asynchronous execution. The value cannot be less than `max_wasm_stack`.
///
/// The amount of stack space guaranteed for host functions is
/// `async_stack_size - max_wasm_stack`, so take care not to set these two values
/// close to one another; doing so may cause host functions to overflow the
/// stack and abort the process.
///
/// `async_stack_size` must be set prior to setting an instance allocation
/// strategy.
///
/// By default this option is 2 MiB.
#[cfg(feature = "async")]
pub fn async_stack_size(&mut self, size: usize) -> Result<&mut Self> {
if size < self.max_wasm_stack {
bail!("async stack size cannot be less than the maximum wasm stack size");
}
if self.instance_allocator.is_some() {
bail!(
"async stack size cannot be modified after setting an instance allocation strategy"
);
}
self.async_stack_size = size;
Ok(self)
}
/// Configures whether the WebAssembly threads proposal will be enabled for
@@ -504,12 +815,51 @@ impl Config {
Ok(self)
}
/// Sets a custom memory creator
/// Sets a custom memory creator.
///
/// Custom memory creators are used when creating host `Memory` objects or when
/// creating instance linear memories for the on-demand instance allocation strategy.
pub fn with_host_memory(&mut self, mem_creator: Arc<dyn MemoryCreator>) -> &mut Self {
self.memory_creator = Some(MemoryCreatorProxy { mem_creator });
self.default_instance_allocator =
OnDemandInstanceAllocator::new(Some(Arc::new(MemoryCreatorProxy(mem_creator))));
self
}
/// Sets the instance allocation strategy to use.
///
/// When using the pooling instance allocation strategy, all linear memories will be created as "static".
///
/// This means the [`Config::static_memory_maximum_size`] and [`Config::static_memory_guard_size`] options
/// will be ignored in favor of [`InstanceLimits::memory_reservation_size`] when the pooling instance
/// allocation strategy is used.
pub fn with_allocation_strategy(
&mut self,
strategy: InstanceAllocationStrategy,
) -> Result<&mut Self> {
self.instance_allocator = match strategy {
InstanceAllocationStrategy::OnDemand => None,
InstanceAllocationStrategy::Pooling {
strategy,
module_limits,
instance_limits,
} => {
#[cfg(feature = "async")]
let stack_size = self.async_stack_size;
#[cfg(not(feature = "async"))]
let stack_size = 0;
Some(Arc::new(PoolingInstanceAllocator::new(
strategy.into(),
module_limits.into(),
instance_limits.into(),
stack_size,
)?))
}
};
Ok(self)
}
/// Configures the maximum size, in bytes, where a linear memory is
/// considered static, above which it'll be considered dynamic.
///
@@ -726,7 +1076,15 @@ impl Config {
pub(crate) fn build_compiler(&self) -> Compiler {
let isa = self.target_isa();
Compiler::new(isa, self.strategy, self.tunables.clone(), self.features)
let mut tunables = self.tunables.clone();
self.instance_allocator().adjust_tunables(&mut tunables);
Compiler::new(isa, self.strategy, tunables, self.features)
}
pub(crate) fn instance_allocator(&self) -> &dyn InstanceAllocator {
self.instance_allocator
.as_deref()
.unwrap_or(&self.default_instance_allocator)
}
}

View File

@@ -547,10 +547,13 @@ impl Table {
bail!("cross-`Store` table copies are not supported");
}
if dst_table.ty() != src_table.ty() {
bail!("tables do not have the same element type");
}
// NB: We must use the `dst_table`'s `wasmtime_handle` for the
// `dst_table_index` and vice versa for `src_table` since each table can
// come from different modules.
let dst_table_index = dst_table.wasmtime_table_index();
let dst_table_index = dst_table.instance.get_defined_table(dst_table_index);
@@ -579,6 +582,11 @@ impl Table {
bail!("cross-`Store` table fills are not supported");
}
// Ensure the fill value is the correct type
if self.ty().element() != &val.ty() {
bail!("mismatched element fill type");
}
let table_index = self.wasmtime_table_index();
self.instance
.handle

View File

@@ -12,9 +12,9 @@ use wasmtime_environ::wasm::{
};
use wasmtime_environ::Initializer;
use wasmtime_runtime::{
Imports, InstantiationError, RuntimeInstance, StackMapRegistry, VMContext,
VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
VMTableImport,
Imports, InstanceAllocationRequest, InstantiationError, RuntimeInstance, StackMapRegistry,
VMContext, VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport,
VMMemoryImport, VMTableImport,
};
/// An instantiated WebAssembly module.
@@ -492,18 +492,26 @@ impl<'a> Instantiator<'a> {
// compiled JIT code within the `Store`.
self.store.register_module(&self.cur.module);
let config = self.store.engine().config();
unsafe {
let instance = compiled_module.instantiate(
self.cur.build(),
&self.store.lookup_shared_signature(self.cur.module.types()),
config.memory_creator.as_ref().map(|a| a as _),
self.store.interrupts(),
Box::new(()),
self.store.externref_activations_table() as *const VMExternRefActivationsTable
let config = self.store.engine().config();
let allocator = config.instance_allocator();
let instance = allocator.allocate(InstanceAllocationRequest {
module: compiled_module.module().clone(),
finished_functions: compiled_module.finished_functions(),
imports: self.cur.build(),
lookup_shared_signature: &self
.store
.lookup_shared_signature(self.cur.module.types()),
host_state: Box::new(()),
interrupts: self.store.interrupts(),
externref_activations_table: self.store.externref_activations_table()
as *const VMExternRefActivationsTable
as *mut _,
self.store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?;
stack_map_registry: self.store.stack_map_registry() as *const StackMapRegistry
as *mut _,
})?;
// After we've created the `InstanceHandle` we still need to run
// initialization to set up data/elements/etc. We do this after adding
@@ -512,12 +520,9 @@ impl<'a> Instantiator<'a> {
// initializers may have run which placed elements into other instance's
// tables. This means that from this point on, regardless of whether
// initialization is successful, we need to keep the instance alive.
let instance = self.store.add_instance(instance);
instance
.initialize(
config.features.bulk_memory,
&compiled_module.data_initializers(),
)
let instance = self.store.add_instance(instance, false);
allocator
.initialize(&instance.handle, config.features.bulk_memory)
.map_err(|e| -> Error {
match e {
InstantiationError::Trap(trap) => {

View File

@@ -172,6 +172,12 @@
//! * `vtune` - Not enabled by default, this feature compiles in support for
//! supporting VTune profiling of JIT code.
//!
//! * `uffd` - Not enabled by default. This feature enables `userfaultfd` support
//! when using the pooling instance allocator. As handling page faults in user space
//! comes with a performance penalty, this feature should only be enabled when kernel
//! lock contention is hampering multithreading throughput. This feature is only
//! supported on Linux and requires a Linux kernel version 4.11 or higher.
//!
//! ## Examples
//!
//! In addition to the examples below be sure to check out the [online embedding

View File

@@ -307,15 +307,22 @@ impl Module {
/// # }
/// ```
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
#[cfg(feature = "cache")]
let (main_module, artifacts, types) =
ModuleCacheEntry::new("wasmtime", engine.cache_config())
const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux"));
cfg_if::cfg_if! {
if #[cfg(feature = "cache")] {
let (main_module, artifacts, types) = ModuleCacheEntry::new(
"wasmtime",
engine.cache_config(),
)
.get_data((engine.compiler(), binary), |(compiler, binary)| {
CompilationArtifacts::build(compiler, binary)
CompilationArtifacts::build(compiler, binary, USE_PAGED_MEM_INIT)
})?;
#[cfg(not(feature = "cache"))]
let (main_module, artifacts, types) =
CompilationArtifacts::build(engine.compiler(), binary)?;
} else {
let (main_module, artifacts, types) =
CompilationArtifacts::build(engine.compiler(), binary, USE_PAGED_MEM_INIT)?;
}
};
let mut modules = CompiledModule::from_artifacts_list(
artifacts,
@@ -324,6 +331,12 @@ impl Module {
)?;
let module = modules.remove(main_module);
// Validate the module can be used with the current allocator
engine
.config()
.instance_allocator()
.validate(module.module())?;
Ok(Module {
inner: Arc::new(ModuleInner {
engine: engine.clone(),

View File

@@ -18,10 +18,19 @@ use std::task::{Context, Poll};
use wasmtime_environ::wasm;
use wasmtime_jit::{CompiledModule, ModuleCode, TypeTables};
use wasmtime_runtime::{
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMContext,
InstanceAllocator, InstanceHandle, SignalHandler, StackMapRegistry, TrapInfo, VMContext,
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
};
/// Used to associate instances with the store.
///
/// This is needed to track if the instance was allocated expliclty with the default
/// instance allocator.
struct StoreInstance {
handle: InstanceHandle,
use_default_allocator: bool,
}
/// A `Store` is a collection of WebAssembly instances and host-defined items.
///
/// All WebAssembly instances and items will be attached to and refer to a
@@ -63,7 +72,7 @@ pub(crate) struct StoreInner {
engine: Engine,
interrupts: Arc<VMInterrupts>,
signatures: RefCell<SignatureRegistry>,
instances: RefCell<Vec<InstanceHandle>>,
instances: RefCell<Vec<StoreInstance>>,
signal_handler: RefCell<Option<Box<SignalHandler<'static>>>>,
externref_activations_table: VMExternRefActivationsTable,
stack_map_registry: StackMapRegistry,
@@ -254,15 +263,6 @@ impl Store {
&self.inner.engine
}
/// Returns an optional reference to a ['RuntimeMemoryCreator']
pub(crate) fn memory_creator(&self) -> Option<&dyn RuntimeMemoryCreator> {
self.engine()
.config()
.memory_creator
.as_ref()
.map(|x| x as _)
}
pub(crate) fn signatures(&self) -> &RefCell<SignatureRegistry> {
&self.inner.signatures
}
@@ -383,8 +383,15 @@ impl Store {
Ok(())
}
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(handle.clone());
pub(crate) unsafe fn add_instance(
&self,
handle: InstanceHandle,
use_default_allocator: bool,
) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(StoreInstance {
handle: handle.clone(),
use_default_allocator,
});
StoreInstanceHandle {
store: self.clone(),
handle,
@@ -397,7 +404,7 @@ impl Store {
.instances
.borrow()
.iter()
.any(|i| i.vmctx_ptr() == handle.vmctx_ptr()));
.any(|i| i.handle.vmctx_ptr() == handle.vmctx_ptr()));
StoreInstanceHandle {
store: self.clone(),
handle,
@@ -752,12 +759,14 @@ impl Store {
/// that the various comments are illuminating as to what's going on here.
#[cfg(feature = "async")]
pub(crate) async fn on_fiber<R>(&self, func: impl FnOnce() -> R) -> Result<R, Trap> {
debug_assert!(self.is_async());
let config = self.inner.engine.config();
// TODO: allocation of a fiber should be much more abstract where we
// shouldn't be allocating huge stacks on every async wasm function call.
debug_assert!(self.is_async());
debug_assert!(config.async_stack_size > 0);
type SuspendType = wasmtime_fiber::Suspend<Result<(), Trap>, (), Result<(), Trap>>;
let mut slot = None;
let fiber = wasmtime_fiber::Fiber::new(10 * 1024 * 1024, |keep_going, suspend| {
let func = |keep_going, suspend: &SuspendType| {
// First check and see if we were interrupted/dropped, and only
// continue if we haven't been.
keep_going?;
@@ -775,18 +784,46 @@ impl Store {
slot = Some(func());
Ok(())
})
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
};
let (fiber, stack) = match config.instance_allocator().allocate_fiber_stack() {
Ok(stack) => {
// Use the returned stack and deallocate it when finished
(
unsafe {
wasmtime_fiber::Fiber::new_with_stack(stack, func)
.map_err(|e| Trap::from(anyhow::Error::from(e)))?
},
stack,
)
}
Err(wasmtime_runtime::FiberStackError::NotSupported) => {
// The allocator doesn't support custom fiber stacks for the current platform
// Request that the fiber itself allocate the stack
(
wasmtime_fiber::Fiber::new(config.async_stack_size, func)
.map_err(|e| Trap::from(anyhow::Error::from(e)))?,
std::ptr::null_mut(),
)
}
Err(e) => return Err(Trap::from(anyhow::Error::from(e))),
};
// Once we have the fiber representing our synchronous computation, we
// wrap that in a custom future implementation which does the
// translation from the future protocol to our fiber API.
FiberFuture { fiber, store: self }.await?;
FiberFuture {
fiber,
store: self,
stack,
}
.await?;
return Ok(slot.unwrap());
struct FiberFuture<'a> {
fiber: wasmtime_fiber::Fiber<'a, Result<(), Trap>, (), Result<(), Trap>>,
store: &'a Store,
stack: *mut u8,
}
impl Future for FiberFuture<'_> {
@@ -843,15 +880,23 @@ impl Store {
// completion.
impl Drop for FiberFuture<'_> {
fn drop(&mut self) {
if self.fiber.done() {
return;
if !self.fiber.done() {
let result = self.fiber.resume(Err(Trap::new("future dropped")));
// This resumption with an error should always complete the
// fiber. While it's technically possible for host code to catch
// the trap and re-resume, we'd ideally like to signal that to
// callers that they shouldn't be doing that.
debug_assert!(result.is_ok());
}
if !self.stack.is_null() {
unsafe {
self.store
.engine()
.config()
.instance_allocator()
.deallocate_fiber_stack(self.stack)
};
}
let result = self.fiber.resume(Err(Trap::new("future dropped")));
// This resumption with an error should always complete the
// fiber. While it's technically possible for host code to catch
// the trap and re-resume, we'd ideally like to signal that to
// callers that they shouldn't be doing that.
debug_assert!(result.is_ok());
}
}
}
@@ -974,9 +1019,17 @@ impl fmt::Debug for Store {
impl Drop for StoreInner {
fn drop(&mut self) {
for instance in self.instances.get_mut().iter() {
let allocator = self.engine.config().instance_allocator();
for instance in self.instances.borrow().iter() {
unsafe {
instance.dealloc();
if instance.use_default_allocator {
self.engine
.config()
.default_instance_allocator
.deallocate(&instance.handle);
} else {
allocator.deallocate(&instance.handle);
}
}
}
}

View File

@@ -9,15 +9,15 @@ use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::Module;
use wasmtime_runtime::{
Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
VMFunctionImport, VMSharedSignatureIndex,
Imports, InstanceAllocationRequest, InstanceAllocator, StackMapRegistry,
VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMSharedSignatureIndex,
};
pub(crate) fn create_handle(
module: Module,
store: &Store,
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
state: Box<dyn Any>,
host_state: Box<dyn Any>,
func_imports: &[VMFunctionImport],
shared_signature_id: Option<VMSharedSignatureIndex>,
) -> Result<StoreInstanceHandle> {
@@ -26,17 +26,26 @@ pub(crate) fn create_handle(
let module = Arc::new(module);
unsafe {
let handle = InstanceHandle::new(
module,
&finished_functions,
imports,
store.memory_creator(),
&|_| shared_signature_id.unwrap(),
state,
store.interrupts(),
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?;
Ok(store.add_instance(handle))
// Use the default allocator when creating handles associated with host objects
// The configured instance allocator should only be used when creating module instances
// as we don't want host objects to count towards instance limits.
let handle = store
.engine()
.config()
.default_instance_allocator
.allocate(InstanceAllocationRequest {
module: module.clone(),
finished_functions: &finished_functions,
imports,
lookup_shared_signature: &|_| shared_signature_id.unwrap(),
host_state,
interrupts: store.interrupts(),
externref_activations_table: store.externref_activations_table()
as *const VMExternRefActivationsTable
as *mut _,
stack_map_registry: store.stack_map_registry() as *const StackMapRegistry as *mut _,
})?;
Ok(store.add_instance(handle, true))
}
}

View File

@@ -3,7 +3,7 @@ use crate::memory::{LinearMemory, MemoryCreator};
use crate::trampoline::StoreInstanceHandle;
use crate::Store;
use crate::{Limits, MemoryType};
use anyhow::Result;
use anyhow::{anyhow, Result};
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::{wasm, MemoryPlan, MemoryStyle, Module, WASM_PAGE_SIZE};
use wasmtime_runtime::{RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefinition};
@@ -54,19 +54,18 @@ impl RuntimeLinearMemory for LinearMemoryProxy {
}
#[derive(Clone)]
pub(crate) struct MemoryCreatorProxy {
pub(crate) mem_creator: Arc<dyn MemoryCreator>,
}
pub(crate) struct MemoryCreatorProxy(pub Arc<dyn MemoryCreator>);
impl RuntimeMemoryCreator for MemoryCreatorProxy {
fn new_memory(&self, plan: &MemoryPlan) -> Result<Box<dyn RuntimeLinearMemory>, String> {
fn new_memory(&self, plan: &MemoryPlan) -> Result<Box<dyn RuntimeLinearMemory>> {
let ty = MemoryType::new(Limits::new(plan.memory.minimum, plan.memory.maximum));
let reserved_size_in_bytes = match plan.style {
MemoryStyle::Static { bound } => Some(bound as u64 * WASM_PAGE_SIZE as u64),
MemoryStyle::Dynamic => None,
};
self.mem_creator
self.0
.new_memory(ty, reserved_size_in_bytes, plan.offset_guard_size)
.map(|mem| Box::new(LinearMemoryProxy { mem }) as Box<dyn RuntimeLinearMemory>)
.map_err(|e| anyhow!(e))
}
}