This PR introduces a new way of performing cooperative timeslicing that is intended to replace the "fuel" mechanism. The tradeoff is that this mechanism interrupts with less precision: not at deterministic points where fuel runs out, but rather when the Engine enters a new epoch. The generated code instrumentation is substantially faster, however, because it does not need to do as much work as when tracking fuel; it only loads the global "epoch counter" and does a compare-and-branch at backedges and function prologues. This change has been measured as ~twice as fast as fuel-based timeslicing for some workloads, especially control-flow-intensive workloads such as the SpiderMonkey JS interpreter on Wasm/WASI. The intended interface is that the embedder of the `Engine` performs an `engine.increment_epoch()` call periodically, e.g. once per millisecond. An async invocation of a Wasm guest on a `Store` can specify a number of epoch-ticks that are allowed before an async yield back to the executor's event loop. (The initial amount and automatic "refills" are configured on the `Store`, just as for fuel.) This call does only signal-safe work (it increments an `AtomicU64`) so could be invoked from a periodic signal, or from a thread that wakes up once per period.
101 lines
4.2 KiB
Rust
101 lines
4.2 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
/// Tunable parameters for WebAssembly compilation.
|
|
#[derive(Clone, Hash, Serialize, Deserialize)]
|
|
pub struct Tunables {
|
|
/// For static heaps, the size in wasm pages of the heap protected by bounds checking.
|
|
pub static_memory_bound: u64,
|
|
|
|
/// The size in bytes of the offset guard for static heaps.
|
|
pub static_memory_offset_guard_size: u64,
|
|
|
|
/// The size in bytes of the offset guard for dynamic heaps.
|
|
pub dynamic_memory_offset_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<T>` has
|
|
/// space not in use to grow into.
|
|
pub dynamic_memory_growth_reserve: u64,
|
|
|
|
/// Whether or not to generate native DWARF debug information.
|
|
pub generate_native_debuginfo: bool,
|
|
|
|
/// Whether or not to retain DWARF sections in compiled modules.
|
|
pub parse_wasm_debuginfo: bool,
|
|
|
|
/// Whether or not to enable the ability to interrupt wasm code dynamically.
|
|
///
|
|
/// More info can be found about the implementation in
|
|
/// crates/environ/src/cranelift.rs. Note that you can't interrupt host
|
|
/// calls and interrupts are implemented through the `VMInterrupts`
|
|
/// structure, or `InterruptHandle` in the `wasmtime` crate.
|
|
pub interruptable: bool,
|
|
|
|
/// Whether or not fuel is enabled for generated code, meaning that fuel
|
|
/// will be consumed every time a wasm instruction is executed.
|
|
pub consume_fuel: bool,
|
|
|
|
/// 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 memory allocations will have a guard region at the
|
|
/// beginning of the allocation in addition to the end.
|
|
pub guard_before_linear_memory: bool,
|
|
|
|
/// Indicates whether an address map from compiled native code back to wasm
|
|
/// offsets in the original file is generated.
|
|
pub generate_address_map: bool,
|
|
}
|
|
|
|
impl Default for Tunables {
|
|
fn default() -> Self {
|
|
let (static_memory_bound, static_memory_offset_guard_size) =
|
|
if cfg!(target_pointer_width = "64") {
|
|
// 64-bit has tons of address space to static memories can have 4gb
|
|
// address space reservations liberally by default, allowing us to
|
|
// help eliminate bounds checks.
|
|
//
|
|
// Coupled with a 2 GiB address space guard it lets us translate
|
|
// wasm offsets into x86 offsets as aggressively as we can.
|
|
(0x1_0000, 0x8000_0000)
|
|
} else if cfg!(target_pointer_width = "32") {
|
|
// 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.
|
|
((10 * (1 << 20)) / crate::WASM_PAGE_SIZE as u64, 0x1_0000)
|
|
} else {
|
|
panic!("unsupported target_pointer_width");
|
|
};
|
|
Self {
|
|
static_memory_bound,
|
|
static_memory_offset_guard_size,
|
|
|
|
// Size in bytes of the offset guard for dynamic memories.
|
|
//
|
|
// Allocate a small guard to optimize common cases but without
|
|
// wasting too much memory.
|
|
dynamic_memory_offset_guard_size: 0x1_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.
|
|
#[cfg(target_pointer_width = "64")]
|
|
dynamic_memory_growth_reserve: 2 << 30, // 2GB
|
|
#[cfg(target_pointer_width = "32")]
|
|
dynamic_memory_growth_reserve: 1 << 20, // 1MB
|
|
|
|
generate_native_debuginfo: false,
|
|
parse_wasm_debuginfo: true,
|
|
interruptable: false,
|
|
consume_fuel: false,
|
|
epoch_interruption: false,
|
|
static_memory_bound_is_maximum: false,
|
|
guard_before_linear_memory: true,
|
|
generate_address_map: true,
|
|
}
|
|
}
|
|
}
|