use wasmtime::{
Config, Engine, InstanceAllocationStrategy, OptLevel, PoolingAllocationConfig, StoreLimits,
StoreLimitsBuilder, Strategy,
};
use crate::error::WasmtimeRuntimeError;
pub const DEFAULT_MEMORY_MAX_PAGES: u32 = 2048;
pub const DEFAULT_MEMORY_RESERVATION_BYTES: u64 = 256 * 1024 * 1024;
pub const DEFAULT_MEMORY_GUARD_BYTES: u64 = 64 * 1024 * 1024;
pub const DEFAULT_POOL_TOTAL_MEMORIES: u32 = 64;
pub const DEFAULT_POOL_TOTAL_CORE_INSTANCES: u32 = 64;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum HotEngineAllocationStrategy {
#[default]
OnDemand,
Pooling,
}
#[derive(Debug)]
pub struct HostState {
limits: StoreLimits,
}
impl HostState {
pub fn new(memory_max_pages: u32) -> Self {
Self {
limits: StoreLimitsBuilder::new()
.memory_size((memory_max_pages as usize) << 16)
.build(),
}
}
pub fn limits(&mut self) -> &mut StoreLimits {
&mut self.limits
}
}
impl Default for HostState {
fn default() -> Self {
Self::new(DEFAULT_MEMORY_MAX_PAGES)
}
}
#[derive(Clone, Debug)]
pub struct HotEngineConfig {
pub allocation_strategy: HotEngineAllocationStrategy,
pub memory_max_pages: u32,
pub max_wasm_stack: usize,
pub pool_total_memories: u32,
pub pool_total_core_instances: u32,
pub memory_reservation_bytes: u64,
pub memory_guard_bytes: u64,
pub plugin_failure_policy: crate::policy::PluginFailurePolicy,
pub shape_origin_policy: crate::policy::ShapeOriginPolicy,
pub wire_bounds: crate::policy::PluginWireBounds,
pub cookie_redaction: bool,
}
impl Default for HotEngineConfig {
fn default() -> Self {
Self {
allocation_strategy: HotEngineAllocationStrategy::OnDemand,
memory_max_pages: DEFAULT_MEMORY_MAX_PAGES,
max_wasm_stack: 1024 * 1024,
pool_total_memories: DEFAULT_POOL_TOTAL_MEMORIES,
pool_total_core_instances: DEFAULT_POOL_TOTAL_CORE_INSTANCES,
memory_reservation_bytes: DEFAULT_MEMORY_RESERVATION_BYTES,
memory_guard_bytes: DEFAULT_MEMORY_GUARD_BYTES,
plugin_failure_policy: crate::policy::PluginFailurePolicy::PassThrough,
shape_origin_policy: crate::policy::ShapeOriginPolicy::Unrestricted,
wire_bounds: crate::policy::PluginWireBounds::default(),
cookie_redaction: false,
}
}
}
impl HotEngineConfig {
pub fn max_memory_size_bytes(&self) -> u64 {
u64::from(self.memory_max_pages) << 16
}
pub fn virtual_memory_reservation_bytes(&self) -> u64 {
match self.allocation_strategy {
HotEngineAllocationStrategy::OnDemand => 0,
HotEngineAllocationStrategy::Pooling => u64::from(self.pool_total_memories)
.saturating_mul(
self.memory_reservation_bytes
.saturating_add(self.memory_guard_bytes),
),
}
}
}
pub fn build_hot_engine(cfg: &HotEngineConfig) -> Result<Engine, WasmtimeRuntimeError> {
let mut wcfg = Config::new();
wcfg.strategy(Strategy::Cranelift)
.cranelift_opt_level(OptLevel::Speed)
.wasm_component_model(false)
.wasm_threads(false)
.wasm_memory64(false)
.wasm_multi_memory(false)
.wasm_function_references(false)
.wasm_gc(false)
.wasm_exceptions(false)
.wasm_tail_call(false)
.wasm_relaxed_simd(false)
.signals_based_traps(true)
.memory_reservation(cfg.memory_reservation_bytes)
.memory_guard_size(cfg.memory_guard_bytes)
.memory_init_cow(true)
.wasm_backtrace(false)
.coredump_on_trap(false)
.native_unwind_info(false)
.max_wasm_stack(cfg.max_wasm_stack);
match cfg.allocation_strategy {
HotEngineAllocationStrategy::OnDemand => {
wcfg.allocation_strategy(InstanceAllocationStrategy::OnDemand);
}
HotEngineAllocationStrategy::Pooling => {
let max_memory_size = (cfg.memory_max_pages as usize) << 16;
let mut pool = PoolingAllocationConfig::new();
pool.total_memories(cfg.pool_total_memories)
.total_core_instances(cfg.pool_total_core_instances)
.max_memory_size(max_memory_size);
wcfg.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
}
}
Engine::new(&wcfg).map_err(|e| WasmtimeRuntimeError::EngineInit(anyhow::Error::from(e)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_memory_max_pages_covers_files_body_cap_with_margin() {
let cfg = HotEngineConfig::default();
assert_eq!(
cfg.memory_max_pages, DEFAULT_MEMORY_MAX_PAGES,
"default must accommodate 100 MiB /v1/files body with rkyv envelope + scratch margin",
);
}
#[test]
fn default_allocation_strategy_is_on_demand() {
let cfg = HotEngineConfig::default();
assert_eq!(
cfg.allocation_strategy,
HotEngineAllocationStrategy::OnDemand
);
assert_eq!(cfg.pool_total_memories, 64);
assert_eq!(cfg.pool_total_core_instances, 64);
assert_eq!(cfg.memory_reservation_bytes, 256 * 1024 * 1024);
assert_eq!(cfg.memory_guard_bytes, 64 * 1024 * 1024);
assert_eq!(cfg.max_memory_size_bytes(), 128 * 1024 * 1024);
assert_eq!(cfg.virtual_memory_reservation_bytes(), 0);
}
#[test]
fn pooling_strategy_uses_configured_virtual_reservation() {
let cfg = HotEngineConfig {
allocation_strategy: HotEngineAllocationStrategy::Pooling,
..HotEngineConfig::default()
};
assert_eq!(
cfg.virtual_memory_reservation_bytes(),
20 * 1024 * 1024 * 1024
);
}
}