Expose memory-related options in Config (#1513)

* Expose memory-related options in `Config`

This commit was initially motivated by looking more into #1501, but it
ended up balooning a bit after finding a few issues. The high-level
items in this commit are:

* New configuration options via `wasmtime::Config` are exposed to
  configure the tunable limits of how memories are allocated and such.
* The `MemoryCreator` trait has been updated to accurately reflect the
  required allocation characteristics that JIT code expects.
* A bug has been fixed in the cranelift wasm code generation where if no
  guard page was present bounds checks weren't accurately performed.

The new `Config` methods allow tuning the memory allocation
characteristics of wasmtime. Currently 64-bit platforms will reserve 6GB
chunks of memory for each linear memory, but by tweaking various config
options you can change how this is allocate, perhaps at the cost of
slower JIT code since it needs more bounds checks. The methods are
intended to be pretty thoroughly documented as to the effect they have
on the JIT code and what values you may wish to select. These new
methods have been added to the spectest fuzzer to ensure that various
configuration values for these methods don't affect correctness.

The `MemoryCreator` trait previously only allocated memories with a
`MemoryType`, but this didn't actually reflect the guarantees that JIT
code expected. JIT code is generated with an assumption about the
minimum size of the guard region, as well as whether memory is static or
dynamic (whether the base pointer can be relocated). These properties
must be upheld by custom allocation engines for JIT code to perform
correctly, so extra parameters have been added to
`MemoryCreator::new_memory` to reflect this.

Finally the fuzzing with `Config` turned up an issue where if no guard
pages present the wasm code wouldn't correctly bounds-check memory
accesses. The issue here was that with a guard page we only need to
bounds-check the first byte of access, but without a guard page we need
to bounds-check the last byte of access. This meant that the code
generation needed to account for the size of the memory operation
(load/store) and use this as the offset-to-check in the no-guard-page
scenario. I've attempted to make the various comments in cranelift a bit
more exhaustive too to hopefully make it a bit clearer for future
readers!

Closes #1501

* Review comments

* Update a comment
This commit is contained in:
Alex Crichton
2020-04-29 19:10:00 -05:00
committed by GitHub
parent bc4b4707e3
commit 363cd2d20f
11 changed files with 430 additions and 52 deletions

View File

@@ -870,8 +870,59 @@ pub unsafe trait LinearMemory {
/// Note that this is a relatively new and experimental feature and it is recommended
/// to be familiar with wasmtime runtime code to use it.
pub unsafe trait MemoryCreator: Send + Sync {
/// Create new LinearMemory
fn new_memory(&self, ty: MemoryType) -> Result<Box<dyn LinearMemory>, String>;
/// Create a new `LinearMemory` object from the specified parameters.
///
/// The type of memory being created is specified by `ty` which indicates
/// both the minimum and maximum size, in wasm pages.
///
/// The `reserved_size` value indicates the expected size of the
/// reservation that is to be made for this memory. If this value is `None`
/// than the implementation is free to allocate memory as it sees fit. If
/// the value is `Some`, however, then the implementation is expected to
/// reserve that many bytes for the memory's allocation, plus the guard
/// size at the end. Note that this reservation need only be a virtual
/// memory reservation, physical memory does not need to be allocated
/// immediately. In this case `grow` should never move the base pointer and
/// the maximum size of `ty` is guaranteed to fit within `reserved_size`.
///
/// The `guard_size` parameter indicates how many bytes of space, after the
/// memory allocation, is expected to be unmapped. JIT code will elide
/// bounds checks based on the `guard_size` provided, so for JIT code to
/// work correctly the memory returned will need to be properly guarded with
/// `guard_size` bytes left unmapped after the base allocation.
///
/// Note that the `reserved_size` and `guard_size` options are tuned from
/// the various [`Config`](crate::Config) methods about memory
/// sizes/guards. Additionally these two values are guaranteed to be
/// multiples of the system page size.
fn new_memory(
&self,
ty: MemoryType,
reserved_size: Option<u64>,
guard_size: u64,
) -> Result<Box<dyn LinearMemory>, String>;
}
#[cfg(test)]
mod tests {
use crate::*;
// Assert that creating a memory via `Memory::new` respects the limits/tunables
// in `Config`.
#[test]
fn respect_tunables() {
let mut cfg = Config::new();
cfg.static_memory_maximum_size(0)
.dynamic_memory_guard_size(0);
let store = Store::new(&Engine::new(&cfg));
let ty = MemoryType::new(Limits::new(1, None));
let mem = Memory::new(&store, ty);
assert_eq!(mem.wasmtime_export.memory.offset_guard_size, 0);
match mem.wasmtime_export.memory.style {
wasmtime_environ::MemoryStyle::Dynamic => {}
other => panic!("unexpected style {:?}", other),
}
}
}
// Exports