From a4084db09660d9cf51bc84d3dc625f1e9b228c4c Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 4 Mar 2021 22:27:27 -0800 Subject: [PATCH] More feedback changes. * Don't reexport types from `wasmtime_runtime` from the `wasmtime` crate. * Add more comments. --- crates/runtime/src/instance.rs | 6 +- .../runtime/src/instance/allocator/pooling.rs | 60 ++--- crates/wasmtime/src/config.rs | 228 +++++++++++++++++- 3 files changed, 241 insertions(+), 53 deletions(-) diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 7c06c61211..9475e47517 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -381,7 +381,11 @@ impl Instance { /// Returns `None` if memory can't be grown by the specified amount /// of pages. pub(crate) fn memory_grow(&self, memory_index: DefinedMemoryIndex, delta: u32) -> Option { - // Reset all guard pages before growing any memory + // Reset all guard pages before growing any memory when using the uffd feature. + // The uffd feature induces a trap when a fault on a linear memory page is determined to be out-of-bounds. + // It does this by temporarily setting the protection level to `NONE` to cause the kernel to signal SIGBUS. + // Because instances might still be used after a trap, this resets the page back to the expected protection + // level (READ_WRITE) for the uffd implementation. #[cfg(all(feature = "uffd", target_os = "linux"))] self.reset_guard_pages().ok()?; diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs index c9e054cbc1..18ddf2bc42 100644 --- a/crates/runtime/src/instance/allocator/pooling.rs +++ b/crates/runtime/src/instance/allocator/pooling.rs @@ -55,53 +55,37 @@ fn round_up_to_pow2(n: usize, to: usize) -> usize { /// Represents the limits placed on a module for compiling with the pooling instance allocator. #[derive(Debug, Copy, Clone)] pub struct ModuleLimits { - /// The maximum number of imported functions for a module (default is 1000). + /// The maximum number of imported functions for a module. pub imported_functions: u32, - /// The maximum number of imported tables for a module (default is 0). + /// The maximum number of imported tables for a module. pub imported_tables: u32, - /// The maximum number of imported linear memories for a module (default is 0). + /// The maximum number of imported linear memories for a module. pub imported_memories: u32, - /// The maximum number of imported globals for a module (default is 0). + /// The maximum number of imported globals for a module. pub imported_globals: u32, - /// The maximum number of defined types for a module (default is 100). + /// The maximum number of defined types for a module. pub types: u32, - /// The maximum number of defined functions for a module (default is 10000). + /// The maximum number of defined functions for a module. pub functions: u32, - /// The maximum number of defined tables for a module (default is 1). + /// The maximum number of defined tables for a module. pub tables: u32, - /// The maximum number of defined linear memories for a module (default is 1). + /// The maximum number of defined linear memories for a module. pub memories: u32, - /// The maximum number of defined globals for a module (default is 10). + /// The maximum number of defined globals for a module. 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. + /// The maximum table elements for any table defined in a module. 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. + /// The maximum number of pages for any linear memory defined in a module. pub memory_pages: u32, } @@ -224,7 +208,7 @@ impl ModuleLimits { impl Default for ModuleLimits { fn default() -> Self { - // See doc comments for `ModuleLimits` for these default values + // See doc comments for `wasmtime::ModuleLimits` for these default values Self { imported_functions: 1000, imported_tables: 0, @@ -244,32 +228,16 @@ impl Default for ModuleLimits { /// Represents the limits placed on instances by the pooling instance allocator. #[derive(Debug, Copy, Clone)] pub struct InstanceLimits { - /// The maximum number of concurrent instances supported (default is 1000). + /// The maximum number of concurrent instances supported. 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 offet 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 { - // See doc comments for `InstanceLimits` for these default values + // See doc comments for `wasmtime::InstanceLimits` for these default values Self { count: 1000, #[cfg(target_pointer_width = "32")] diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 30e63546e8..fdce7b9202 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -16,8 +16,213 @@ use wasmtime_jit::{native, CompilationStrategy, Compiler}; use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent}; use wasmtime_runtime::{InstanceAllocator, OnDemandInstanceAllocator, PoolingInstanceAllocator}; -// Re-export the limit structures for the pooling allocator -pub use wasmtime_runtime::{InstanceLimits, ModuleLimits, PoolingAllocationStrategy}; +/// 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 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 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 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)] @@ -44,6 +249,17 @@ pub enum InstanceAllocationStrategy { }, } +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 @@ -66,7 +282,7 @@ pub struct Config { pub(crate) profiler: Arc, pub(crate) instance_allocator: Option>, // The default instance allocator is used for instantiating host objects - // and for module instatiation when `instance_allocator` is None + // 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, @@ -628,9 +844,9 @@ impl Config { let stack_size = 0; Some(Arc::new(PoolingInstanceAllocator::new( - strategy, - module_limits, - instance_limits, + strategy.into(), + module_limits.into(), + instance_limits.into(), stack_size, )?)) }