Merge pull request #2518 from peterhuene/add-allocator
Implement the pooling instance allocator.
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user