From 6b210017e6c2b756599cf87a34ebad6bcd49af6b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 2 Nov 2024 13:22:19 -0700 Subject: [PATCH 1/3] Remove static/dynamic memories from public docs This commit removes the terminology of "static" and "dynamic" memories from the public-facing documentation of Wasmtime, notably on the `Config` structure and its various configuration settings. The goal of this commit is in the same vein as #9543 which is to simplify the memory settings of Wasmtime for users in this case. This change doesn't actually have any code changes beyond renames (and handling now-deprecated CLI options). The goal of this commit is to basically rewrite how we document the effect of various settings of Wasmtime. Notably: * `Config::static_memory_maximum_size` is now `memory_reservation`. * `Config::static_memory_forced` is now `memory_reservation_is_maximum`. * `Config::dynamic_memory_reserved_for_growth` is now `memory_reservation_for_growth`. Documentation for all of these options has been rewritten and updated to take into account the removal of "dynamic" and "static" terminology. Additionally more words have been written about the various effects of each setting and how things related to wasm features such as index type sizes and custom page sizes. The rewritten documentation is intended to basically already match what Wasmtime does today. I believe that all of these settings are useful in one form or another so none have been dropped but the updated documentation is intended to help simplify the mental model for how they're processed internally and how they affect allocations and such. --- crates/c-api/include/wasmtime/config.h | 20 +- crates/c-api/src/config.rs | 12 +- crates/cli-flags/src/lib.rs | 45 +- crates/environ/src/module.rs | 25 +- crates/environ/src/tunables.rs | 33 +- crates/fuzzing/src/generators/config.rs | 16 +- crates/fuzzing/src/generators/memory.rs | 14 +- crates/misc/component-test-util/src/lib.rs | 2 +- crates/wasmtime/src/config.rs | 472 +++++++++++------- crates/wasmtime/src/engine/serialization.rs | 18 +- crates/wasmtime/src/runtime/memory.rs | 2 +- crates/wasmtime/src/runtime/vm/cow.rs | 10 +- .../runtime/vm/instance/allocator/pooling.rs | 6 +- .../instance/allocator/pooling/memory_pool.rs | 18 +- examples/mpk.rs | 8 +- src/commands/serve.rs | 2 +- tests/all/async_functions.rs | 6 +- tests/all/fuel.rs | 2 +- tests/all/memory.rs | 24 +- tests/all/memory_creator.rs | 2 +- tests/all/module.rs | 2 +- tests/all/module_serialize.rs | 2 +- tests/all/pooling_allocator.rs | 14 +- tests/pcc_memory.rs | 10 +- tests/wast.rs | 10 +- 25 files changed, 452 insertions(+), 323 deletions(-) diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h index 2130d4ff6c91..21df3271ebec 100644 --- a/crates/c-api/include/wasmtime/config.h +++ b/crates/c-api/include/wasmtime/config.h @@ -316,22 +316,24 @@ WASMTIME_CONFIG_PROP(void, cranelift_opt_level, wasmtime_opt_level_t) WASMTIME_CONFIG_PROP(void, profiler, wasmtime_profiling_strategy_t) /** - * \brief Configures the “static” style of memory to always be used. + * \brief Configures whether `memory_reservation` is the maximal size of linear + * memory. * * This setting is `false` by default. * * For more information see the Rust documentation at - * https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.static_memory_forced. + * https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.memory_may_move. */ -WASMTIME_CONFIG_PROP(void, static_memory_forced, bool) +WASMTIME_CONFIG_PROP(void, memory_may_move, bool) /** - * \brief Configures the maximum size for memory to be considered "static" + * \brief Configures the size, in bytes, of initial memory reservation size for + * linear memories. * * For more information see the Rust documentation at - * https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.static_memory_maximum_size. + * https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.memory_reservation. */ -WASMTIME_CONFIG_PROP(void, static_memory_maximum_size, uint64_t) +WASMTIME_CONFIG_PROP(void, memory_reservation, uint64_t) /** * \brief Configures the guard region size for linear memory. @@ -343,12 +345,12 @@ WASMTIME_CONFIG_PROP(void, memory_guard_size, uint64_t) /** * \brief Configures the size, in bytes, of the extra virtual memory space - * reserved after a “dynamic” memory for growing into. + * reserved for memories to grow into after being relocated. * * For more information see the Rust documentation at - * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.dynamic_memory_reserved_for_growth + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.memory_reservation_for_growth */ -WASMTIME_CONFIG_PROP(void, dynamic_memory_reserved_for_growth, uint64_t) +WASMTIME_CONFIG_PROP(void, memory_reservation_for_growth, uint64_t) /** * \brief Configures whether to generate native unwind information (e.g. diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index f2c149d29076..96dd3384a7a9 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -222,13 +222,13 @@ pub unsafe extern "C" fn wasmtime_config_cache_config_load( } #[no_mangle] -pub extern "C" fn wasmtime_config_static_memory_forced_set(c: &mut wasm_config_t, enable: bool) { - c.config.static_memory_forced(enable); +pub extern "C" fn wasmtime_config_memory_may_move_set(c: &mut wasm_config_t, enable: bool) { + c.config.memory_may_move(enable); } #[no_mangle] -pub extern "C" fn wasmtime_config_static_memory_maximum_size_set(c: &mut wasm_config_t, size: u64) { - c.config.static_memory_maximum_size(size); +pub extern "C" fn wasmtime_config_memory_reservation_set(c: &mut wasm_config_t, size: u64) { + c.config.memory_reservation(size); } #[no_mangle] @@ -237,11 +237,11 @@ pub extern "C" fn wasmtime_config_memory_guard_size_set(c: &mut wasm_config_t, s } #[no_mangle] -pub extern "C" fn wasmtime_config_dynamic_memory_reserved_for_growth_set( +pub extern "C" fn wasmtime_config_memory_reservation_reserved_for_growth_set( c: &mut wasm_config_t, size: u64, ) { - c.config.dynamic_memory_reserved_for_growth(size); + c.config.memory_reservation_for_growth(size); } #[no_mangle] diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 746302699f97..f45b1e59ca60 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -42,20 +42,18 @@ wasmtime_option_group! { /// Optimization level of generated code (0-2, s; default: 2) pub opt_level: Option, - /// Force using a "static" style for all wasm memories - pub static_memory_forced: Option, + /// Do not allow memories to grow beyond `-O memory-reservation` + pub memory_may_move: Option, - /// Maximum size in bytes of wasm memory before it becomes dynamically - /// relocatable instead of up-front-reserved. - pub static_memory_maximum_size: Option, + /// Initial virtual memory allocation size for memories. + pub memory_reservation: Option, + + /// Bytes to reserve at the end of linear memory for growth into. + pub memory_reservation_for_growth: Option, /// Size, in bytes, of guard pages for linear memories. pub memory_guard_size: Option, - /// Bytes to reserve at the end of linear memory for growth for dynamic - /// memories. - pub dynamic_memory_reserved_for_growth: Option, - /// Indicates whether an unmapped region of memory is placed before all /// linear memories. pub guard_before_linear_memory: Option, @@ -170,6 +168,15 @@ wasmtime_option_group! { /// DEPRECATED: Use `-Cmemory-guard-size=N` instead. pub static_memory_guard_size: Option, + + /// DEPRECATED: Use `-Cmemory-may-move` instead. + pub static_memory_forced: Option, + + /// DEPRECATED: Use `-Cmemory-reservation=N` instead. + pub static_memory_maximum_size: Option, + + /// DEPRECATED: Use `-Cmemory-reservation-for-growth=N` instead. + pub dynamic_memory_reserved_for_growth: Option, } enum Optimize { @@ -631,12 +638,16 @@ impl CommonOptions { true => err, } - if let Some(max) = self.opts.static_memory_maximum_size { - config.static_memory_maximum_size(max); + if let Some(max) = self + .opts + .memory_reservation + .or(self.opts.static_memory_maximum_size) + { + config.memory_reservation(max); } - if let Some(enable) = self.opts.static_memory_forced { - config.static_memory_forced(enable); + if let Some(enable) = self.opts.memory_may_move.or(self.opts.static_memory_forced) { + config.memory_may_move(enable); } if let Some(size) = self @@ -648,8 +659,12 @@ impl CommonOptions { config.memory_guard_size(size); } - if let Some(size) = self.opts.dynamic_memory_reserved_for_growth { - config.dynamic_memory_reserved_for_growth(size); + if let Some(size) = self + .opts + .memory_reservation_for_growth + .or(self.opts.dynamic_memory_reserved_for_growth) + { + config.memory_reservation_for_growth(size); } if let Some(enable) = self.opts.guard_before_linear_memory { config.guard_before_linear_memory(enable); diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 176e9dd30f1f..8b179b41c919 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -35,28 +35,29 @@ impl MemoryStyle { && tunables.signals_based_traps && match memory.maximum_byte_size() { Ok(mut maximum) => { - if tunables.static_memory_bound_is_maximum { - maximum = maximum.min(tunables.static_memory_reservation); + if tunables.memory_may_move { + maximum = maximum.min(tunables.memory_reservation); } - // Ensure the minimum is less than the maximum; the minimum might exceed the maximum - // when the memory is artificially bounded via `static_memory_bound_is_maximum` above + // Ensure the minimum is less than the maximum; the minimum + // might exceed the maximum when the memory is artificially + // bounded via `memory_may_move` above memory.minimum_byte_size().unwrap() <= maximum - && maximum <= tunables.static_memory_reservation + && maximum <= tunables.memory_reservation } // If the maximum size of this memory is not representable with - // `u64` then use the `static_memory_bound_is_maximum` to indicate - // whether it's a static memory or not. It should be ok to discard - // the linear memory's maximum size here as growth to the maximum is - // always fallible and never guaranteed. - Err(_) => tunables.static_memory_bound_is_maximum, + // `u64` then use the `memory_may_move` to indicate whether + // it's a static memory or not. It should be ok to discard the + // linear memory's maximum size here as growth to the maximum + // is always fallible and never guaranteed. + Err(_) => tunables.memory_may_move, }; if is_static { return ( Self::Static { - byte_reservation: tunables.static_memory_reservation, + byte_reservation: tunables.memory_reservation, }, tunables.memory_guard_size, ); @@ -65,7 +66,7 @@ impl MemoryStyle { // Otherwise, make it dynamic. ( Self::Dynamic { - reserve: tunables.dynamic_memory_growth_reserve, + reserve: tunables.memory_reservation_for_growth, }, tunables.memory_guard_size, ) diff --git a/crates/environ/src/tunables.rs b/crates/environ/src/tunables.rs index ca46268c3fb6..63268ae1cd71 100644 --- a/crates/environ/src/tunables.rs +++ b/crates/environ/src/tunables.rs @@ -62,18 +62,15 @@ define_tunables! { /// GC objects and barriers that must be emitted in Wasm code. pub collector: Option, - /// For static heaps, the size in bytes of virtual memory reservation for - /// the heap. - pub static_memory_reservation: u64, + /// Initial size, in bytes, to be allocated for linear memories. + pub memory_reservation: u64, /// The size, in bytes, of the guard page region for linear memories. pub memory_guard_size: u64, - /// The size, in bytes, of reserved memory at the end of a "dynamic" memory, - /// before the guard page, that memory can grow into. This is intended to - /// amortize the cost of `memory.grow` in the same manner that `Vec` has - /// space not in use to grow into. - pub dynamic_memory_growth_reserve: u64, + /// The size, in bytes, to allocate at the end of a relocated linear + /// memory for growth. + pub memory_reservation_for_growth: u64, /// Whether or not to generate native DWARF debug information. pub generate_native_debuginfo: bool, @@ -88,9 +85,9 @@ define_tunables! { /// Whether or not we use epoch-based interruption. pub epoch_interruption: bool, - /// Whether or not to treat the static memory bound as the maximum for - /// unbounded heaps. - pub static_memory_bound_is_maximum: bool, + /// Whether or not linear memories are allowed to be reallocated after + /// initial allocation at runtime. + pub memory_may_move: bool, /// Whether or not linear memory allocations will have a guard region at the /// beginning of the allocation in addition to the end. @@ -160,9 +157,9 @@ impl Tunables { // No virtual memory tricks are available on miri so make these // limits quite conservative. - static_memory_reservation: 1 << 20, + memory_reservation: 1 << 20, memory_guard_size: 0, - dynamic_memory_growth_reserve: 0, + memory_reservation_for_growth: 0, // General options which have the same defaults regardless of // architecture. @@ -170,7 +167,7 @@ impl Tunables { parse_wasm_debuginfo: true, consume_fuel: false, epoch_interruption: false, - static_memory_bound_is_maximum: false, + memory_may_move: true, guard_before_linear_memory: true, table_lazy_init: true, generate_address_map: true, @@ -187,9 +184,9 @@ impl Tunables { // For 32-bit we scale way down to 10MB of reserved memory. This // impacts performance severely but allows us to have more than a // few instances running around. - static_memory_reservation: 10 * (1 << 20), + memory_reservation: 10 * (1 << 20), memory_guard_size: 0x1_0000, - dynamic_memory_growth_reserve: 1 << 20, // 1MB + memory_reservation_for_growth: 1 << 20, // 1MB ..Tunables::default_miri() } @@ -204,14 +201,14 @@ impl Tunables { // // Coupled with a 2 GiB address space guard it lets us translate // wasm offsets into x86 offsets as aggressively as we can. - static_memory_reservation: 1 << 32, + memory_reservation: 1 << 32, memory_guard_size: 0x8000_0000, // We've got lots of address space on 64-bit so use a larger // grow-into-this area, but on 32-bit we aren't as lucky. Miri is // not exactly fast so reduce memory consumption instead of trying // to avoid memory movement. - dynamic_memory_growth_reserve: 2 << 30, // 2GB + memory_reservation_for_growth: 2 << 30, // 2GB ..Tunables::default_miri() } diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs index 1f79cda5a473..d14e34cf923f 100644 --- a/crates/fuzzing/src/generators/config.rs +++ b/crates/fuzzing/src/generators/config.rs @@ -92,7 +92,7 @@ impl Config { pooling.core_instance_size = 1_000_000; if let MemoryConfig::Normal(cfg) = &mut self.wasmtime.memory_config { - match &mut cfg.static_memory_maximum_size { + match &mut cfg.memory_reservation { Some(size) => *size = (*size).max(pooling.max_memory_size as u64), other @ None => *other = Some(pooling.max_memory_size as u64), } @@ -266,9 +266,9 @@ impl Config { // supported when bounds checks are elided. let memory_config = if pcc { MemoryConfig::Normal(NormalMemoryConfig { - static_memory_maximum_size: Some(4 << 30), // 4 GiB - memory_guard_size: Some(2 << 30), // 2 GiB - dynamic_memory_reserved_for_growth: Some(0), + memory_reservation: Some(4 << 30), // 4 GiB + memory_guard_size: Some(2 << 30), // 2 GiB + memory_reservation_for_growth: Some(0), guard_before_linear_memory: false, memory_init_cow: true, // Doesn't matter, only using virtual memory. @@ -284,9 +284,9 @@ impl Config { } MemoryConfig::CustomUnaligned => { cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) - .static_memory_maximum_size(0) + .memory_reservation(0) .memory_guard_size(0) - .dynamic_memory_reserved_for_growth(0) + .memory_reservation_for_growth(0) .guard_before_linear_memory(false) .memory_init_cow(false); } @@ -519,7 +519,7 @@ impl WasmtimeConfig { .min(config.max_memory64_bytes.try_into().unwrap_or(u64::MAX)); let mut min = min_bytes.min(pooling.max_memory_size as u64); if let MemoryConfig::Normal(cfg) = &self.memory_config { - min = min.min(cfg.static_memory_maximum_size.unwrap_or(0)); + min = min.min(cfg.memory_reservation.unwrap_or(0)); } pooling.max_memory_size = min as usize; config.max_memory32_bytes = min; @@ -534,7 +534,7 @@ impl WasmtimeConfig { config.max_memory32_bytes = 1 << 16; config.max_memory64_bytes = 1 << 16; if let MemoryConfig::Normal(cfg) = &mut self.memory_config { - match &mut cfg.static_memory_maximum_size { + match &mut cfg.memory_reservation { Some(size) => *size = (*size).max(pooling.max_memory_size as u64), size @ None => *size = Some(pooling.max_memory_size as u64), } diff --git a/crates/fuzzing/src/generators/memory.rs b/crates/fuzzing/src/generators/memory.rs index d6eb7554f3bf..a5f542875a6e 100644 --- a/crates/fuzzing/src/generators/memory.rs +++ b/crates/fuzzing/src/generators/memory.rs @@ -135,9 +135,9 @@ pub enum MemoryConfig { #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[allow(missing_docs)] pub struct NormalMemoryConfig { - pub static_memory_maximum_size: Option, + pub memory_reservation: Option, pub memory_guard_size: Option, - pub dynamic_memory_reserved_for_growth: Option, + pub memory_reservation_for_growth: Option, pub guard_before_linear_memory: bool, pub cranelift_enable_heap_access_spectre_mitigations: Option, pub memory_init_cow: bool, @@ -148,9 +148,9 @@ impl<'a> Arbitrary<'a> for NormalMemoryConfig { // This attempts to limit memory and guard sizes to 32-bit ranges so // we don't exhaust a 64-bit address space easily. Ok(Self { - static_memory_maximum_size: as Arbitrary>::arbitrary(u)?.map(Into::into), + memory_reservation: as Arbitrary>::arbitrary(u)?.map(Into::into), memory_guard_size: as Arbitrary>::arbitrary(u)?.map(Into::into), - dynamic_memory_reserved_for_growth: as Arbitrary>::arbitrary(u)? + memory_reservation_for_growth: as Arbitrary>::arbitrary(u)? .map(Into::into), guard_before_linear_memory: u.arbitrary()?, cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?, @@ -163,11 +163,9 @@ impl NormalMemoryConfig { /// Apply this memory configuration to the given `wasmtime::Config`. pub fn apply_to(&self, config: &mut wasmtime::Config) { config - .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0)) + .memory_reservation(self.memory_reservation.unwrap_or(0)) .memory_guard_size(self.memory_guard_size.unwrap_or(0)) - .dynamic_memory_reserved_for_growth( - self.dynamic_memory_reserved_for_growth.unwrap_or(0), - ) + .memory_reservation_for_growth(self.memory_reservation_for_growth.unwrap_or(0)) .guard_before_linear_memory(self.guard_before_linear_memory) .memory_init_cow(self.memory_init_cow); diff --git a/crates/misc/component-test-util/src/lib.rs b/crates/misc/component-test-util/src/lib.rs index c5f42b874f1c..eddb7654722f 100644 --- a/crates/misc/component-test-util/src/lib.rs +++ b/crates/misc/component-test-util/src/lib.rs @@ -55,7 +55,7 @@ pub fn config() -> Config { // component model tests create a disproportionate number of instances so // try to cut down on virtual memory usage by avoiding 4G reservations. if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() { - config.static_memory_maximum_size(0); + config.memory_reservation(0); config.memory_guard_size(0); } config diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 62f24389cc21..a8b36c7374be 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1010,7 +1010,7 @@ impl Config { /// type. /// /// This is part of the transition plan in - /// https://github.com/WebAssembly/component-model/issues/370. + /// . #[cfg(feature = "component-model")] pub fn wasm_component_model_more_flags(&mut self, enable: bool) -> &mut Self { self.wasm_feature(WasmFeatures::COMPONENT_MODEL_MORE_FLAGS, enable); @@ -1020,7 +1020,7 @@ impl Config { /// Configures whether components support more than one return value for functions. /// /// This is part of the transition plan in - /// https://github.com/WebAssembly/component-model/pull/368. + /// . #[cfg(feature = "component-model")] pub fn wasm_component_model_multiple_returns(&mut self, enable: bool) -> &mut Self { self.wasm_feature(WasmFeatures::COMPONENT_MODEL_MULTIPLE_RETURNS, enable); @@ -1291,11 +1291,8 @@ impl Config { /// Sets the instance allocation strategy to use. /// - /// When using the pooling instance allocation strategy, all linear memories - /// will be created as "static" and the - /// [`Config::static_memory_maximum_size`] and - /// [`Config::memory_guard_size`] options will be used to configure - /// the virtual memory allocations of linear memories. + /// This is notably used in conjunction with + /// [`InstanceAllocationStrategy::Pooling`] and [`PoolingAllocationConfig`]. pub fn allocation_strategy( &mut self, strategy: impl Into, @@ -1304,116 +1301,217 @@ impl Config { self } - /// Configures the maximum size, in bytes, where a linear memory is - /// considered static, above which it'll be considered dynamic. + /// Specifies the capacity of linear memories, in bytes, in their initial + /// allocation. /// /// > Note: this value has important performance ramifications, be sure to - /// > understand what this value does before tweaking it and benchmarking. + /// > benchmark when setting this to a non-default value and read over this + /// > documentation. + /// + /// This function will change the size of the initial memory allocation made + /// for linear memories. This setting is only applicable when the initial + /// size of a linear memory is below this threshold. Linear memories are + /// allocated in the virtual address space of the host process with OS APIs + /// such as `mmap` and this setting affects how large the allocation will + /// be. + /// + /// ## Background: WebAssembly Linear Memories + /// + /// WebAssembly linear memories always start with a minimum size and can + /// possibly grow up to a maximum size. The minimum size is always specified + /// in a WebAssembly module itself and the maximum size can either be + /// optionally specified in the module or inherently limited by the index + /// type. For example for this module: + /// + /// ```wasm + /// (module + /// (memory $a 4) + /// (memory $b 4096 4096 (pagesize 1)) + /// (memory $c i64 10) + /// ) + /// ``` /// - /// This function configures the threshold for wasm memories whether they're - /// implemented as a dynamically relocatable chunk of memory or a statically - /// located chunk of memory. The `max_size` parameter here is the size, in - /// bytes, where if the maximum size of a linear memory is below `max_size` - /// then it will be statically allocated with enough space to never have to - /// move. If the maximum size of a linear memory is larger than `max_size` - /// then wasm memory will be dynamically located and may move in memory - /// through growth operations. - /// - /// Specifying a `max_size` of 0 means that all memories will be dynamic and - /// may be relocated through `memory.grow`. Also note that if any wasm - /// memory's maximum size is below `max_size` then it will still reserve - /// `max_size` bytes in the virtual memory space. - /// - /// ## Static vs Dynamic Memory - /// - /// Linear memories represent contiguous arrays of bytes, but they can also - /// be grown through the API and wasm instructions. When memory is grown if - /// space hasn't been preallocated then growth may involve relocating the - /// base pointer in memory. Memories in Wasmtime are classified in two - /// different ways: - /// - /// * **static** - these memories preallocate all space necessary they'll - /// ever need, meaning that the base pointer of these memories is never - /// moved. Static memories may take more virtual memory space because of - /// pre-reserving space for memories. - /// - /// * **dynamic** - these memories are not preallocated and may move during - /// growth operations. Dynamic memories consume less virtual memory space - /// because they don't need to preallocate space for future growth. - /// - /// Static memories can be optimized better in JIT code because once the - /// base address is loaded in a function it's known that we never need to - /// reload it because it never changes, `memory.grow` is generally a pretty - /// fast operation because the wasm memory is never relocated, and under - /// some conditions bounds checks can be elided on memory accesses. - /// - /// Dynamic memories can't be quite as heavily optimized because the base - /// address may need to be reloaded more often, they may require relocating - /// lots of data on `memory.grow`, and dynamic memories require - /// unconditional bounds checks on all memory accesses. - /// - /// ## Should you use static or dynamic memory? - /// - /// In general you probably don't need to change the value of this property. - /// The defaults here are optimized for each target platform to consume a - /// reasonable amount of physical memory while also generating speedy - /// machine code. - /// - /// One of the main reasons you may want to configure this today is if your - /// environment can't reserve virtual memory space for each wasm linear - /// memory. On 64-bit platforms wasm memories require a 6GB reservation by - /// default, and system limits may prevent this in some scenarios. In this - /// case you may wish to force memories to be allocated dynamically meaning - /// that the virtual memory footprint of creating a wasm memory should be - /// exactly what's used by the wasm itself. - /// - /// For 32-bit memories a static memory must contain at least 4GB of - /// reserved address space plus a guard page to elide any bounds checks at - /// all. Smaller static memories will use similar bounds checks as dynamic - /// memories. + /// * Memory `$a` initially allocates 4 WebAssembly pages (256KiB) and can + /// grow up to 4GiB, the limit of the 32-bit index space. + /// * Memory `$b` initially allocates 4096 WebAssembly pages, but in this + /// case the page size is 1, so it's 4096 bytes. Memory can also grow no + /// further meaning that it will always be 4096 bytes. + /// * Memory `$c` is a 64-bit linear memory which starts with 640KiB of + /// memory and can theoretically grow up to 2^64 bytes, although most + /// hosts will run out of memory long before that. /// - /// ## Default + /// All operations on linear memories done by wasm are required to be + /// in-bounds. Any access beyond the end of a linear memory is considered a + /// trap. /// - /// The default value for this property depends on the host platform. For - /// 64-bit platforms there's lots of address space available, so the default - /// configured here is 4GB. WebAssembly linear memories currently max out at - /// 4GB which means that on 64-bit platforms Wasmtime by default always uses - /// a static memory. This, coupled with a sufficiently sized guard region, - /// should produce the fastest JIT code on 64-bit platforms, but does - /// require a large address space reservation for each wasm memory. + /// ## What this setting affects: Virtual Memory /// - /// For 32-bit platforms this value defaults to 1GB. This means that wasm - /// memories whose maximum size is less than 1GB will be allocated - /// statically, otherwise they'll be considered dynamic. + /// This setting is used to configure the behavior of the size of the linear + /// memory allocation performed for each of these memories. For example the + /// initial linear memory allocation looks like this: /// - /// ## Static Memory and Pooled Instance Allocation + /// ```text + /// memory_reservation + /// | + /// ◄─────────┴────────────────► + /// ┌───────┬─────────┬──────────────────┬───────┐ + /// │ guard │ initial │ ... capacity ... │ guard │ + /// └───────┴─────────┴──────────────────┴───────┘ + /// ◄──┬──► ◄──┬──► + /// │ │ + /// │ memory_guard_size + /// │ + /// │ + /// memory_guard_size (if guard_before_linear_memory) + /// ``` /// - /// When using the pooling instance allocator memories are considered to - /// always be static memories, they are never dynamic. This setting - /// configures the size of linear memory to reserve for each memory in the - /// pooling allocator. + /// Memory in the `initial` range is accessible to the instance and can be + /// read/written by wasm code. Memory in the `guard` regions is never + /// accesible to wasm code and memory in `capacity` is initially + /// inaccessible but may become accesible through `memory.grow` instructions + /// for example. + /// + /// This means that this setting is the size of the initial chunk of virtual + /// memory that a linear memory may grow into. + /// + /// ## What this setting affects: Runtime Speed + /// + /// This is a performance-sensitive setting which is taken into account + /// during the compilation process of a WebAssembly module. For example if a + /// 32-bit WebAssembly linear memory has a `memory_reservation` size of 4GiB + /// then bounds checks can be elided because `capacity` will be guaranteed + /// to be unmapped for all addressible bytes that wasm can access (modulo a + /// few details). + /// + /// If `memory_reservation` was something smaller like 256KiB then that + /// would have a much smaller impact on virtual memory but the compile code + /// would then need to have explicit bounds checks to ensure that + /// loads/stores are in-bounds. + /// + /// The goal of this setting is to enable skipping bounds checks in most + /// modules by default. Some situations which require explicit bounds checks + /// though are: + /// + /// * When `memory_reservation` is smaller than the addressible size of the + /// linear memory. For example if 64-bit linear memories always need + /// bounds checks as they can address the entire virtual address spacce. + /// For 32-bit linear memories a `memory_reservation` minimum size of 4GiB + /// is required to elide bounds checks. + /// + /// * When linear memories have a page size of 1 then bounds checks are + /// required. In this situation virtual memory can't be relied upon + /// because that operates at the host page size granularity where wasm + /// requires a per-byte level granularity. + /// + /// * Configuration settings such as [`Config::signals_based_traps`] can be + /// used to disable the use of signal handlers and virtual memory so + /// explicit bounds checks are required. + /// + /// * When [`Config::memory_guard_size`] is too small a bounds check may be + /// required. For 32-bit wasm addresses are actually 33-bit effective + /// addresses because loads/stores have a 32-bit static offset to add to + /// the dynamic 32-bit address. If the static offset is larger than the + /// size of the guard region then an explicit bounds check is required. + /// + /// ## What this setting affects: Memory Growth Behavior + /// + /// In addition to affecting bounds checks emitted in compiled code this + /// setting also affects how WebAssembly linear memories are grown. The + /// `memory.grow` instruction can be used to make a linear memory larger and + /// this is also affected by APIs such as + /// [`Memory::grow`](crate::Memory::grow). + /// + /// In these situations when the amount being grown is small enough to fit + /// within the remaining capacity then the linear memory doesn't have to be + /// moved at runtime. If the capacity runs out though then a new linear + /// memory allocation must be made and the contents of linear memory is + /// copied over. + /// + /// For example here's a situation where a copy happens: + /// + /// * The `memory_reservation` setting is configured to 128KiB. + /// * A WebAssembly linear memory starts with a single 64KiB page. + /// * This memory can be grown by one page to contain the full 128KiB of + /// memory. + /// * If grown by one more page, though, then a 192KiB allocation must be + /// made and the previous 128KiB of contents are copied into the new + /// allocation. + /// + /// This growth behavior can have a significant performance impact if lots + /// of data needs to be copied on growth. Conversely if memory growth never + /// needs to happen because the capacity will always be large enough then + /// optimizations can be applied to cache the base pointer of linear memory. + /// + /// When memory is grown then the + /// [`Config::memory_reservation_for_growth`] is used for the new + /// memory allocation to have memory to grow into. + /// + /// When using the pooling allocator via [`PoolingAllocationConfig`] then + /// memories are never allowed to move so requests for growth are instead + /// rejected with an error. + /// + /// ## When this setting is not used + /// + /// This setting is ignored and unused when the initial size of linear + /// memory is larger than this threshold. For example if this setting is set + /// to 1MiB but a wasm module requires a 2MiB minimum allocation then this + /// setting is ignored. In this situation the minimum size of memory will be + /// allocated along with [`Config::memory_reservation_for_growth`] + /// after it to grow into. + /// + /// That means that this value can be set to zero. That can be useful in + /// benchmarking to see the overhead of bounds checks for example. + /// Additionally it can be used to minimize the virtual memory allocated by + /// Wasmtime. + /// + /// ## Default Value /// - /// Note that the pooling allocator can reduce the amount of memory needed - /// for pooling allocation by using memory protection; see - /// `PoolingAllocatorConfig::memory_protection_keys` for details. - pub fn static_memory_maximum_size(&mut self, max_size: u64) -> &mut Self { - self.tunables.static_memory_reservation = Some(max_size); + /// The default value for this property depends on the host platform. For + /// 64-bit platforms there's lots of address space available, so the default + /// configured here is 4GiB. When coupled with the default size of + /// [`Config::memory_guard_size`] this means that 32-bit WebAssembly linear + /// memories with 64KiB page sizes will skip almost all bounds checks by + /// default. + /// + /// For 32-bit platforms this value defaults to 10MiB. This means that + /// bounds checks will be required on 32-bit platforms. + pub fn memory_reservation(&mut self, bytes: u64) -> &mut Self { + self.tunables.memory_reservation = Some(bytes); self } - /// Indicates that the "static" style of memory should always be used. + /// Indicates whether linear memories may relocate their base pointer at + /// runtime. + /// + /// WebAssembly linear memories either have a maximum size that's explicitly + /// listed in the type of a memory or inherently limited by the index type + /// of the memory (e.g. 4GiB for 32-bit linear memories). Depending on how + /// the linear memory is allocated (see [`Config::memory_reservation`]) it + /// may be necessary to move the memory in the host's virtual address space + /// during growth. This option controls whether this movement is allowed or + /// not. + /// + /// An example of a linear memory needing to move is when + /// [`Config::memory_reservation`] is 0 then a linear memory will be + /// allocated as the minimum size of the memory plus + /// [`Config::memory_reservation_for_growth`]. When memory grows beyond the + /// reservation for growth then the memory needs to be relocated. + /// + /// When this option is set to `false` then it can have a number of impacts + /// on how memories work at runtime: /// - /// This configuration option enables selecting the "static" option for all - /// linear memories created within this `Config`. This means that all - /// memories will be allocated up-front and will never move. Additionally - /// this means that all memories are synthetically limited by the - /// [`Config::static_memory_maximum_size`] option, regardless of what the - /// actual maximum size is on the memory's original type. + /// * Modules can be compiled with static knowledge the base pointer of + /// linear memory never changes to enable optimizations such as + /// loop-invariant-code-motion (hosting the base pointer out of a loop). /// - /// For the difference between static and dynamic memories, see the - /// [`Config::static_memory_maximum_size`]. - pub fn static_memory_forced(&mut self, force: bool) -> &mut Self { - self.tunables.static_memory_bound_is_maximum = Some(force); + /// * Memories cannot grow in excess of their original allocation. This + /// means that [`Config::memory_reservation`] and + /// [`Config::memory_reservation_for_growth`] may need tuning to ensure + /// the memory work at runtime. + /// + /// The default value for this option is `true`. + pub fn memory_may_move(&mut self, enable: bool) -> &mut Self { + self.tunables.memory_may_move = Some(enable); self } @@ -1423,82 +1521,110 @@ impl Config { /// > Note: this value has important performance ramifications, be sure to /// > understand what this value does before tweaking it and benchmarking. /// - /// All WebAssembly loads/stores are bounds-checked and generate a trap if - /// they're out-of-bounds. Loads and stores are often very performance - /// critical, so we want the bounds check to be as fast as possible! - /// Accelerating these memory accesses is the motivation for a guard after a - /// memory allocation. + /// This setting controls how many bytes are guaranteed to be unmapped after + /// the virtual memory allocation of a linear memory. When + /// combined with sufficiently large values of + /// [`Config::memory_reservation`] (e.g. 4GiB for 32-bit linear memories) + /// then a guard region can be used to eliminate bounds checks in generated + /// code. /// - /// Memories can be configured with a guard at the end of them which - /// consists of unmapped virtual memory. This unmapped memory will trigger - /// a memory access violation (e.g. segfault) if accessed. This allows JIT - /// code to elide bounds checks if it can prove that an access, if out of - /// bounds, would hit the guard region. This means that having such a guard - /// of unmapped memory can remove the need for bounds checks in JIT code. + /// This setting additionally can be used to help deduplicate bounds checks + /// in code that otherwise requires bounds checks. For example with a 4KiB + /// guard region then a 64-bit linear memory which accesses addresses `x+8` + /// and `x+16` only needs to perform a single bounds check on `x`. If that + /// bounds check passes then the offset is guaranteed to either reside in + /// linear memory or the guard region, resulting in deterministic behavior + /// either way. /// /// ## How big should the guard be? /// - /// In general, like with configuring `static_memory_maximum_size`, you - /// probably don't want to change this value from the defaults. Otherwise, - /// though, the size of the guard region affects the number of bounds checks - /// needed for generated wasm code. More specifically, loads/stores with - /// immediate offsets will generate bounds checks based on how big the guard - /// page is. + /// In general, like with configuring [`Config::memory_reservation`], you + /// probably don't want to change this value from the defaults. Removing + /// bounds checks is dependent on a number of factors where the size of the + /// guard region is only one piece of the equation. Other factors include: /// - /// For 32-bit wasm memories a 4GB static memory is required to even start - /// removing bounds checks. A 4GB guard size will guarantee that the module - /// has zero bounds checks for memory accesses. A 2GB guard size will - /// eliminate all bounds checks with an immediate offset less than 2GB. A - /// guard size of zero means that all memory accesses will still have bounds - /// checks. + /// * [`Config::memory_reservation`] + /// * The index type of the linear memory (e.g. 32-bit or 64-bit) + /// * The page size of the linear memory + /// * Other settings such as [`Config::signals_based_traps`] + /// + /// Embeddings using virtual memory almost always want at least some guard + /// region, but otherwise changes from the default should be profiled + /// locally to see the performance impact. /// /// ## Default /// - /// The default value for this property is 2GB on 64-bit platforms. This + /// The default value for this property is 2GiB on 64-bit platforms. This /// allows eliminating almost all bounds checks on loads/stores with an - /// immediate offset of less than 2GB. On 32-bit platforms this defaults to - /// 64KB. - pub fn memory_guard_size(&mut self, guard_size: u64) -> &mut Self { - self.tunables.memory_guard_size = Some(guard_size); + /// immediate offset of less than 2GiB. On 32-bit platforms this defaults to + /// 64KiB. + pub fn memory_guard_size(&mut self, bytes: u64) -> &mut Self { + self.tunables.memory_guard_size = Some(bytes); self } /// Configures the size, in bytes, of the extra virtual memory space - /// reserved after a "dynamic" memory for growing into. - /// - /// For the difference between static and dynamic memories, see the - /// [`Config::static_memory_maximum_size`] - /// - /// Dynamic memories can be relocated in the process's virtual address space - /// on growth and do not always reserve their entire space up-front. This - /// means that a growth of the memory may require movement in the address - /// space, which in the worst case can copy a large number of bytes from one - /// region to another. - /// - /// This setting configures how many bytes are reserved after the initial - /// reservation for a dynamic memory for growing into. A value of 0 here - /// means that no extra bytes are reserved and all calls to `memory.grow` - /// will need to relocate the wasm linear memory (copying all the bytes). A - /// value of 1 megabyte, however, means that `memory.grow` can allocate up - /// to a megabyte of extra memory before the memory needs to be moved in - /// linear memory. + /// reserved after a linear memory is relocated. + /// + /// This setting is used in conjunction with [`Config::memory_reservation`] + /// to configure what happens after a linear memory is relocated in the host + /// address space. If the initial size of a linear memory exceeds + /// [`Config::memory_reservation`] or if it grows beyond that size + /// throughout its lifetime then this setting will be used. + /// + /// When a linear memory is relocated it will initially look like this: + /// + /// ```text + /// memory.size + /// │ + /// ◄──────┴─────► + /// ┌───────┬──────────────┬───────┐ + /// │ guard │ accessible │ guard │ + /// └───────┴──────────────┴───────┘ + /// ◄──┬──► + /// │ + /// memory_guard_size + /// ``` + /// + /// where `accessible` needs to be grown but there's no more memory to grow + /// into. A new region of the virtual address space will be allocated that + /// looks like this: + /// + /// ```text + /// memory_reservation_for_growth + /// │ + /// memory.size │ + /// │ │ + /// ◄──────┴─────► ◄─────────────┴───────────► + /// ┌───────┬──────────────┬───────────────────────────┬───────┐ + /// │ guard │ accessible │ .. reserved for growth .. │ guard │ + /// └───────┴──────────────┴───────────────────────────┴───────┘ + /// ◄──┬──► + /// │ + /// memory_guard_size + /// ``` + /// + /// This means that up to `memory_reservation_for_growth` bytes can be + /// allocated again before the entire linear memory needs to be moved again + /// when another `memory_reservation_for_growth` bytes will be appended to + /// the size of the allocation. /// /// Note that this is a currently simple heuristic for optimizing the growth /// of dynamic memories, primarily implemented for the memory64 proposal - /// where all memories are currently "dynamic". This is unlikely to be a - /// one-size-fits-all style approach and if you're an embedder running into - /// issues with dynamic memories and growth and are interested in having + /// where the maximum size of memory is larger than 4GiB. This setting is + /// unlikely to be a one-size-fits-all style approach and if you're an + /// embedder running into issues with growth and are interested in having /// other growth strategies available here please feel free to [open an /// issue on the Wasmtime repository][issue]! /// - /// [issue]: https://github.com/bytecodealliance/wasmtime/issues/ne + /// [issue]: https://github.com/bytecodealliance/wasmtime/issues/new /// /// ## Default /// - /// For 64-bit platforms this defaults to 2GB, and for 32-bit platforms this - /// defaults to 1MB. - pub fn dynamic_memory_reserved_for_growth(&mut self, reserved: u64) -> &mut Self { - self.tunables.dynamic_memory_growth_reserve = Some(reserved); + /// For 64-bit platforms this defaults to 2GiB, and for 32-bit platforms + /// this defaults to 1MiB. + pub fn memory_reservation_for_growth(&mut self, bytes: u64) -> &mut Self { + self.tunables.memory_reservation_for_growth = Some(bytes); self } @@ -1516,14 +1642,13 @@ impl Config { /// /// The size of the guard region before linear memory is the same as the /// guard size that comes after linear memory, which is configured by - /// [`Config::static_memory_guard_size`] and - /// [`Config::dynamic_memory_guard_size`]. + /// [`Config::memory_guard_size`]. /// /// ## Default /// /// This value defaults to `true`. - pub fn guard_before_linear_memory(&mut self, guard: bool) -> &mut Self { - self.tunables.guard_before_linear_memory = Some(guard); + pub fn guard_before_linear_memory(&mut self, enable: bool) -> &mut Self { + self.tunables.guard_before_linear_memory = Some(enable); self } @@ -2561,16 +2686,17 @@ pub enum WasmBacktraceDetails { /// /// Another benefit of pooled allocation is that it's possible to configure /// things such that no virtual memory management is required at all in a steady -/// state. For example a pooling allocator can be configured with -/// [`Config::memory_init_cow`] disabledd, dynamic bounds checks enabled -/// through -/// [`Config::static_memory_maximum_size(0)`](Config::static_memory_maximum_size), -/// and sufficient space through -/// [`PoolingAllocationConfig::table_keep_resident`] / -/// [`PoolingAllocationConfig::linear_memory_keep_resident`]. With all these -/// options in place no virtual memory tricks are used at all and everything is -/// manually managed by Wasmtime (for example resetting memory is a -/// `memset(0)`). This is not as fast in a single-threaded scenario but can +/// state. For example a pooling allocator can be configured with: +/// +/// * [`Config::memory_init_cow`] disabled +/// * [`Config::memory_guard_size`] disabled +/// * [`Config::memory_reservation`] shrunk to minimal size +/// * [`PoolingAllocationConfig::table_keep_resident`] sufficiently large +/// * [`PoolingAllocationConfig::linear_memory_keep_resident`] sufficiently large +/// +/// With all these options in place no virtual memory tricks are used at all and +/// everything is manually managed by Wasmtime (for example resetting memory is +/// a `memset(0)`). This is not as fast in a single-threaded scenario but can /// provide benefits in high-parallelism situations as no virtual memory locks /// or IPIs need happen. /// @@ -3041,8 +3167,8 @@ impl PoolingAllocationConfig { /// [`memory_protection_keys`](PoolingAllocationConfig::memory_protection_keys). /// /// The virtual memory reservation size of each linear memory is controlled - /// by the [`Config::static_memory_maximum_size`] setting and this method's - /// configuration cannot exceed [`Config::static_memory_maximum_size`]. + /// by the [`Config::memory_reservation`] setting and this method's + /// configuration cannot exceed [`Config::memory_reservation`]. pub fn max_memory_size(&mut self, bytes: usize) -> &mut Self { self.config.limits.max_memory_size = bytes; self diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 1c1818b102c2..f4bdd79eb0ce 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -364,20 +364,20 @@ impl Metadata<'_> { fn check_tunables(&mut self, other: &Tunables) -> Result<()> { let Tunables { collector, - static_memory_reservation, + memory_reservation, memory_guard_size, generate_native_debuginfo, parse_wasm_debuginfo, consume_fuel, epoch_interruption, - static_memory_bound_is_maximum, + memory_may_move, guard_before_linear_memory, table_lazy_init, relaxed_simd_deterministic, winch_callable, signals_based_traps, // This doesn't affect compilation, it's just a runtime setting. - dynamic_memory_growth_reserve: _, + memory_reservation_for_growth: _, // This does technically affect compilation but modules with/without // trap information can be loaded into engines with the opposite @@ -391,9 +391,9 @@ impl Metadata<'_> { Self::check_collector(collector, other.collector)?; Self::check_int( - static_memory_reservation, - other.static_memory_reservation, - "static memory reservation", + memory_reservation, + other.memory_reservation, + "memory reservation", )?; Self::check_int( memory_guard_size, @@ -416,11 +416,7 @@ impl Metadata<'_> { other.epoch_interruption, "epoch interruption", )?; - Self::check_bool( - static_memory_bound_is_maximum, - other.static_memory_bound_is_maximum, - "pooling allocation support", - )?; + Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?; Self::check_bool( guard_before_linear_memory, other.guard_before_linear_memory, diff --git a/crates/wasmtime/src/runtime/memory.rs b/crates/wasmtime/src/runtime/memory.rs index 8450e388443b..65bbc077a46f 100644 --- a/crates/wasmtime/src/runtime/memory.rs +++ b/crates/wasmtime/src/runtime/memory.rs @@ -1048,7 +1048,7 @@ mod tests { #[test] fn respect_tunables() { let mut cfg = Config::new(); - cfg.static_memory_maximum_size(0).memory_guard_size(0); + cfg.memory_reservation(0).memory_guard_size(0); let mut store = Store::new(&Engine::new(&cfg).unwrap(), ()); let ty = MemoryType::new(1, None); let mem = Memory::new(&mut store, ty).unwrap(); diff --git a/crates/wasmtime/src/runtime/vm/cow.rs b/crates/wasmtime/src/runtime/vm/cow.rs index 7cf3c1672198..0ac6e5d6bea5 100644 --- a/crates/wasmtime/src/runtime/vm/cow.rs +++ b/crates/wasmtime/src/runtime/vm/cow.rs @@ -798,7 +798,7 @@ mod test { fn instantiate_no_image() { let ty = dummy_memory(); let tunables = Tunables { - static_memory_reservation: 4 << 30, + memory_reservation: 4 << 30, ..Tunables::default_miri() }; // 4 MiB mmap'd area, not accessible @@ -838,7 +838,7 @@ mod test { let page_size = host_page_size(); let ty = dummy_memory(); let tunables = Tunables { - static_memory_reservation: 4 << 30, + memory_reservation: 4 << 30, ..Tunables::default_miri() }; // 4 MiB mmap'd area, not accessible @@ -911,7 +911,7 @@ mod test { let page_size = host_page_size(); let ty = dummy_memory(); let tunables = Tunables { - static_memory_reservation: 100 << 16, + memory_reservation: 100 << 16, ..Tunables::default_miri() }; let mut mmap = Mmap::accessible_reserved(0, 4 << 20).unwrap(); @@ -964,8 +964,8 @@ mod test { let page_size = host_page_size(); let ty = dummy_memory(); let tunables = Tunables { - static_memory_reservation: 0, - dynamic_memory_growth_reserve: 200, + memory_reservation: 0, + memory_reservation_for_growth: 200, ..Tunables::default_miri() }; diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs index dbad7bbd9fb7..75e41b562399 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs @@ -136,7 +136,7 @@ pub struct InstanceLimits { pub max_memories_per_module: u32, /// Maximum byte size of a linear memory, must be smaller than - /// `static_memory_reservation` in `Tunables`. + /// `memory_reservation` in `Tunables`. pub max_memory_size: usize, /// The total number of GC heaps in the pool, across all instances. @@ -716,14 +716,14 @@ mod test { PoolingInstanceAllocator::new( &config, &Tunables { - static_memory_reservation: 0x10000, + memory_reservation: 0x10000, ..Tunables::default_host() }, ) .map_err(|e| e.to_string()) .expect_err("expected a failure constructing instance allocator"), "maximum memory size of 0x100010000 bytes exceeds the configured \ - static memory reservation of 0x10000 bytes" + memory reservation of 0x10000 bytes" ); } diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/memory_pool.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/memory_pool.rs index 904a7c49c643..b993e2814a99 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/memory_pool.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/memory_pool.rs @@ -139,14 +139,12 @@ pub struct MemoryPool { impl MemoryPool { /// Create a new `MemoryPool`. pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result { - if u64::try_from(config.limits.max_memory_size).unwrap() - > tunables.static_memory_reservation - { + if u64::try_from(config.limits.max_memory_size).unwrap() > tunables.memory_reservation { bail!( "maximum memory size of {:#x} bytes exceeds the configured \ - static memory reservation of {:#x} bytes", + memory reservation of {:#x} bytes", config.limits.max_memory_size, - tunables.static_memory_reservation + tunables.memory_reservation ); } let pkeys = match config.memory_protection_keys { @@ -556,8 +554,8 @@ impl SlabConstraints { tunables: &Tunables, num_pkeys_available: usize, ) -> Result { - // `static_memory_reservation` is the configured number of bytes for a - // static memory slot (see `Config::static_memory_maximum_size`); even + // `memory_reservation` is the configured number of bytes for a + // static memory slot (see `Config::memory_reservation`); even // if the memory never grows to this size (e.g., it has a lower memory // maximum), codegen will assume that this unused memory is mapped // `PROT_NONE`. Typically `static_memory_bound` is 4GiB which helps @@ -566,9 +564,9 @@ impl SlabConstraints { // MPK-protected stripes, the slot size can be lower than the // `static_memory_bound`. let expected_slot_bytes: usize = tunables - .static_memory_reservation + .memory_reservation .try_into() - .context("static memory bound is too large")?; + .context("memory reservation is too large")?; let expected_slot_bytes = round_usize_up_to_host_pages(expected_slot_bytes)?; let guard_bytes: usize = tunables @@ -781,7 +779,7 @@ mod tests { ..Default::default() }, &Tunables { - static_memory_reservation: WASM_PAGE_SIZE as u64, + memory_reservation: WASM_PAGE_SIZE as u64, memory_guard_size: 0, ..Tunables::default_host() }, diff --git a/examples/mpk.rs b/examples/mpk.rs index 74a708bc120b..236fec3159ac 100644 --- a/examples/mpk.rs +++ b/examples/mpk.rs @@ -69,10 +69,10 @@ struct Args { memory_size: usize, /// The maximum number of bytes a memory is considered static; see - /// `Config::static_memory_maximum_size` for more details and the default + /// `Config::memory_reservation` for more details and the default /// value if unset. #[arg(long, value_parser = parse_byte_size)] - static_memory_maximum_size: Option, + memory_reservation: Option, /// The size in bytes of the guard region to expect between static memory /// slots; see [`Config::memory_guard_size`] for more details and the @@ -192,8 +192,8 @@ fn build_engine(args: &Args, num_memories: u32, enable_mpk: MpkEnabled) -> Resul // Configure the engine itself. let mut config = Config::new(); - if let Some(static_memory_maximum_size) = args.static_memory_maximum_size { - config.static_memory_maximum_size(static_memory_maximum_size); + if let Some(memory_reservation) = args.memory_reservation { + config.memory_reservation(memory_reservation); } if let Some(memory_guard_size) = args.memory_guard_size { config.memory_guard_size(memory_guard_size); diff --git a/src/commands/serve.rs b/src/commands/serve.rs index f3b4643f26ad..02ce3327199f 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -642,7 +642,7 @@ fn use_pooling_allocator_by_default() -> Result> { const BITS_TO_TEST: u32 = 42; let mut config = Config::new(); config.wasm_memory64(true); - config.static_memory_maximum_size(1 << BITS_TO_TEST); + config.memory_reservation(1 << BITS_TO_TEST); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); // NB: the maximum size is in wasm pages to take out the 16-bits of wasm diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs index ae3973b9bb6a..fc891e6ea19a 100644 --- a/tests/all/async_functions.rs +++ b/tests/all/async_functions.rs @@ -353,7 +353,7 @@ async fn async_with_pooling_stacks() { config.async_support(true); config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config).unwrap(); let mut store = Store::new(&engine, ()); @@ -377,7 +377,7 @@ async fn async_host_func_with_pooling_stacks() -> Result<()> { config.async_support(true); config.allocation_strategy(InstanceAllocationStrategy::Pooling(pooling)); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let mut store = Store::new(&Engine::new(&config)?, ()); let mut linker = Linker::new(store.engine()); @@ -409,7 +409,7 @@ async fn async_mpk_protection() -> Result<()> { let mut config = Config::new(); config.async_support(true); config.allocation_strategy(InstanceAllocationStrategy::Pooling(pooling)); - config.static_memory_maximum_size(1 << 26); + config.memory_reservation(1 << 26); config.epoch_interruption(true); let engine = Engine::new(&config)?; diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs index 1d209a30954a..201af9aa5e39 100644 --- a/tests/all/fuel.rs +++ b/tests/all/fuel.rs @@ -182,7 +182,7 @@ fn manual_edge_cases(config: &mut Config) { #[cfg_attr(miri, ignore)] fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping(config: &mut Config) { config.consume_fuel(true); - config.static_memory_maximum_size(0x1_0000); + config.memory_reservation(0x1_0000); let engine = Engine::new(&config).unwrap(); diff --git a/tests/all/memory.rs b/tests/all/memory.rs index 16dbd1d6e375..ece70fb170c7 100644 --- a/tests/all/memory.rs +++ b/tests/all/memory.rs @@ -97,10 +97,10 @@ fn offsets_static_dynamic_oh_my(config: &mut Config) -> Result<()> { let mut engines = Vec::new(); let sizes = [0, 1 * GB, 4 * GB]; - for &static_memory_maximum_size in sizes.iter() { + for &memory_reservation in sizes.iter() { for &guard_size in sizes.iter() { for &guard_before_linear_memory in [true, false].iter() { - config.static_memory_maximum_size(static_memory_maximum_size); + config.memory_reservation(memory_reservation); config.memory_guard_size(guard_size); config.guard_before_linear_memory(guard_before_linear_memory); config.cranelift_debug_verifier(true); @@ -139,7 +139,7 @@ fn guards_present() -> Result<()> { const GUARD_SIZE: u64 = 65536; let mut config = Config::new(); - config.static_memory_maximum_size(1 << 20); + config.memory_reservation(1 << 20); config.memory_guard_size(GUARD_SIZE); config.guard_before_linear_memory(true); let engine = Engine::new(&config)?; @@ -190,7 +190,7 @@ fn guards_present_pooling(config: &mut Config) -> Result<()> { pool.total_memories(2) .max_memory_size(10 << 16) .memory_protection_keys(MpkEnabled::Disable); - config.static_memory_maximum_size(1 << 20); + config.memory_reservation(1 << 20); config.memory_guard_size(GUARD_SIZE); config.guard_before_linear_memory(true); config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); @@ -251,7 +251,7 @@ fn guards_present_pooling_mpk(config: &mut Config) -> Result<()> { .max_memory_size(10 << 16) .memory_protection_keys(MpkEnabled::Enable) .max_memory_protection_keys(2); - config.static_memory_maximum_size(1 << 20); + config.memory_reservation(1 << 20); config.memory_guard_size(GUARD_SIZE); config.guard_before_linear_memory(true); config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); @@ -387,7 +387,7 @@ fn tiny_static_heap(config: &mut Config) -> Result<()> { // the static memory size limit in the configuration. This is intended to // specifically test that a load of all the valid addresses of the memory // all pass bounds-checks in cranelift to help weed out any off-by-one bugs. - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); @@ -420,8 +420,8 @@ fn tiny_static_heap(config: &mut Config) -> Result<()> { #[test] fn static_forced_max() -> Result<()> { let mut config = Config::new(); - config.static_memory_maximum_size(5 << 16); - config.static_memory_forced(true); + config.memory_reservation(5 << 16); + config.memory_may_move(true); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); @@ -434,9 +434,9 @@ fn static_forced_max() -> Result<()> { #[wasmtime_test] fn dynamic_extra_growth_unchanged_pointer(config: &mut Config) -> Result<()> { const EXTRA_PAGES: u64 = 5; - config.static_memory_maximum_size(0); + config.memory_reservation(0); // 5 wasm pages extra - config.dynamic_memory_reserved_for_growth(EXTRA_PAGES * (1 << 16)); + config.memory_reservation_for_growth(EXTRA_PAGES * (1 << 16)); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); @@ -673,8 +673,8 @@ fn init_with_negative_segment(_: &mut Config) -> Result<()> { #[test] fn non_page_aligned_static_memory() -> Result<()> { let mut config = Config::new(); - config.static_memory_maximum_size(100_000); - config.static_memory_forced(true); + config.memory_reservation(100_000); + config.memory_may_move(true); let engine = Engine::new(&config)?; let ty = MemoryType::new(1, None); Memory::new(&mut Store::new(&engine, ()), ty)?; diff --git a/tests/all/memory_creator.rs b/tests/all/memory_creator.rs index 5829f5b0e372..6d79e3c0d3db 100644 --- a/tests/all/memory_creator.rs +++ b/tests/all/memory_creator.rs @@ -129,7 +129,7 @@ mod not_for_windows { let mut config = Config::new(); config .with_host_memory(mem_creator.clone()) - .static_memory_maximum_size(0) + .memory_reservation(0) .memory_guard_size(0); (Store::new(&Engine::new(&config).unwrap(), ()), mem_creator) } diff --git a/tests/all/module.rs b/tests/all/module.rs index 61852409ddb6..7dc7cc9ccd9d 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -37,7 +37,7 @@ fn caches_across_engines() { // differ in runtime settings let res = Module::deserialize( - &Engine::new(Config::new().static_memory_maximum_size(0)).unwrap(), + &Engine::new(Config::new().memory_reservation(0)).unwrap(), &bytes, ); assert!(res.is_err()); diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs index 92f202c56ad6..e3d28576ef8e 100644 --- a/tests/all/module_serialize.rs +++ b/tests/all/module_serialize.rs @@ -68,7 +68,7 @@ fn test_module_serialize_fail() -> Result<()> { )?; let mut config = Config::new(); - config.static_memory_maximum_size(0); + config.memory_reservation(0); let mut store = Store::new(&Engine::new(&config)?, ()); match unsafe { deserialize_and_instantiate(&mut store, &buffer) } { Ok(_) => bail!("expected failure at deserialization"), diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index 162aee1dad35..14474401a735 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -7,7 +7,7 @@ fn successful_instantiation() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?; @@ -27,7 +27,7 @@ fn memory_limit() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(1 << 16); - config.static_memory_maximum_size(3 << 16); + config.memory_reservation(3 << 16); config.wasm_multi_memory(true); let engine = Engine::new(&config)?; @@ -200,7 +200,7 @@ fn memory_zeroed() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; @@ -237,7 +237,7 @@ fn table_limit() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; @@ -372,7 +372,7 @@ fn table_zeroed() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; @@ -407,7 +407,7 @@ fn total_core_instances_limit() -> Result<()> { let mut config = Config::new(); config.allocation_strategy(pool); config.memory_guard_size(0); - config.static_memory_maximum_size(1 << 16); + config.memory_reservation(1 << 16); let engine = Engine::new(&config)?; let module = Module::new(&engine, r#"(module)"#)?; @@ -695,7 +695,7 @@ fn dynamic_memory_pooling_allocator() -> Result<()> { let mut pool = crate::small_pool_config(); pool.max_memory_size(max_size as usize); let mut config = Config::new(); - config.static_memory_maximum_size(max_size); + config.memory_reservation(max_size); config.memory_guard_size(guard_size); config.allocation_strategy(pool); diff --git a/tests/pcc_memory.rs b/tests/pcc_memory.rs index 7b6e3e58d30d..8a992fc85309 100644 --- a/tests/pcc_memory.rs +++ b/tests/pcc_memory.rs @@ -75,18 +75,14 @@ mod pcc_memory_tests { } for test in &bodies { - for static_memory_maximum_size in [4 * GIB] { + for memory_reservation in [4 * GIB] { for guard_size in [2 * GIB] { for enable_spectre in [true /* not yet supported by PCC: false */] { for _memory_bits in [32 /* not yet supported by PCC: 64 */] { log::trace!("test:\n{}\n", test); - log::trace!( - "static {:x} guard {:x}", - static_memory_maximum_size, - guard_size - ); + log::trace!("static {:x} guard {:x}", memory_reservation, guard_size); let mut cfg = Config::new(); - cfg.static_memory_maximum_size(static_memory_maximum_size); + cfg.memory_reservation(memory_reservation); cfg.memory_guard_size(guard_size); cfg.cranelift_pcc(true); unsafe { diff --git a/tests/wast.rs b/tests/wast.rs index dc92dcc4df5f..858690ffe865 100644 --- a/tests/wast.rs +++ b/tests/wast.rs @@ -360,11 +360,11 @@ fn run_wast(wast: &Path, config: WastConfig) -> anyhow::Result<()> { // also don't reserve lots of memory after dynamic memories for growth // (makes growth slower). if use_shared_memory { - cfg.static_memory_maximum_size(2 * u64::from(Memory::DEFAULT_PAGE_SIZE)); + cfg.memory_reservation(2 * u64::from(Memory::DEFAULT_PAGE_SIZE)); } else { - cfg.static_memory_maximum_size(0); + cfg.memory_reservation(0); } - cfg.dynamic_memory_reserved_for_growth(0); + cfg.memory_reservation_for_growth(0); let small_guard = 64 * 1024; cfg.memory_guard_size(small_guard); @@ -390,8 +390,8 @@ fn run_wast(wast: &Path, config: WastConfig) -> anyhow::Result<()> { // impact. let max_memory_size = 805 << 16; if multi_memory { - cfg.static_memory_maximum_size(max_memory_size as u64); - cfg.dynamic_memory_reserved_for_growth(0); + cfg.memory_reservation(max_memory_size as u64); + cfg.memory_reservation_for_growth(0); cfg.memory_guard_size(0); } From 9b362e3c5e355c51a0f93530a3290048648d0a50 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 4 Nov 2024 15:01:18 -0800 Subject: [PATCH 2/3] Fix how the setting is flipped --- crates/cli-flags/src/lib.rs | 5 ++++- crates/environ/src/module.rs | 4 ++-- tests/all/memory.rs | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index f45b1e59ca60..6b6ffb692582 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -646,7 +646,10 @@ impl CommonOptions { config.memory_reservation(max); } - if let Some(enable) = self.opts.memory_may_move.or(self.opts.static_memory_forced) { + if let Some(enable) = self.opts.static_memory_forced { + config.memory_may_move(!enable); + } + if let Some(enable) = self.opts.memory_may_move { config.memory_may_move(enable); } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 8b179b41c919..18cb2db05b43 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -35,7 +35,7 @@ impl MemoryStyle { && tunables.signals_based_traps && match memory.maximum_byte_size() { Ok(mut maximum) => { - if tunables.memory_may_move { + if !tunables.memory_may_move { maximum = maximum.min(tunables.memory_reservation); } @@ -51,7 +51,7 @@ impl MemoryStyle { // it's a static memory or not. It should be ok to discard the // linear memory's maximum size here as growth to the maximum // is always fallible and never guaranteed. - Err(_) => tunables.memory_may_move, + Err(_) => !tunables.memory_may_move, }; if is_static { diff --git a/tests/all/memory.rs b/tests/all/memory.rs index ece70fb170c7..6c8f1c651924 100644 --- a/tests/all/memory.rs +++ b/tests/all/memory.rs @@ -421,7 +421,7 @@ fn tiny_static_heap(config: &mut Config) -> Result<()> { fn static_forced_max() -> Result<()> { let mut config = Config::new(); config.memory_reservation(5 << 16); - config.memory_may_move(true); + config.memory_may_move(false); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); @@ -674,7 +674,7 @@ fn init_with_negative_segment(_: &mut Config) -> Result<()> { fn non_page_aligned_static_memory() -> Result<()> { let mut config = Config::new(); config.memory_reservation(100_000); - config.memory_may_move(true); + config.memory_may_move(false); let engine = Engine::new(&config)?; let ty = MemoryType::new(1, None); Memory::new(&mut Store::new(&engine, ()), ty)?; From d0a295df85a1c7f915d13596787f876aadad9b85 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 4 Nov 2024 16:52:38 -0800 Subject: [PATCH 3/3] Review comments --- crates/cli-flags/src/lib.rs | 3 ++- crates/wasmtime/src/config.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 6b6ffb692582..586e32420d3d 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -42,7 +42,8 @@ wasmtime_option_group! { /// Optimization level of generated code (0-2, s; default: 2) pub opt_level: Option, - /// Do not allow memories to grow beyond `-O memory-reservation` + /// Do not allow Wasm linear memories to move in the host process's + /// address space. pub memory_may_move: Option, /// Initial virtual memory allocation size for memories. diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index a8b36c7374be..4c0de5b0e810 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1334,7 +1334,7 @@ impl Config { /// * Memory `$a` initially allocates 4 WebAssembly pages (256KiB) and can /// grow up to 4GiB, the limit of the 32-bit index space. /// * Memory `$b` initially allocates 4096 WebAssembly pages, but in this - /// case the page size is 1, so it's 4096 bytes. Memory can also grow no + /// case its page size is 1, so it's 4096 bytes. Memory can also grow no /// further meaning that it will always be 4096 bytes. /// * Memory `$c` is a 64-bit linear memory which starts with 640KiB of /// memory and can theoretically grow up to 2^64 bytes, although most @@ -1502,12 +1502,12 @@ impl Config { /// /// * Modules can be compiled with static knowledge the base pointer of /// linear memory never changes to enable optimizations such as - /// loop-invariant-code-motion (hosting the base pointer out of a loop). + /// loop invariant code motion (hoisting the base pointer out of a loop). /// /// * Memories cannot grow in excess of their original allocation. This /// means that [`Config::memory_reservation`] and /// [`Config::memory_reservation_for_growth`] may need tuning to ensure - /// the memory work at runtime. + /// the memory configuration works at runtime. /// /// The default value for this option is `true`. pub fn memory_may_move(&mut self, enable: bool) -> &mut Self {