use crate::externals::MemoryCreator;
use crate::trampoline::{MemoryCreatorProxy, StoreInstanceHandle};
use crate::Module;
use anyhow::{bail, Result};
use std::cell::RefCell;
use std::cmp;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
#[cfg(feature = "cache")]
use std::path::Path;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use target_lexicon::Triple;
use wasmparser::Validator;
#[cfg(feature = "cache")]
use wasmtime_cache::CacheConfig;
use wasmtime_environ::settings::{self, Configurable, SetError};
use wasmtime_environ::{ir, isa, isa::TargetIsa, wasm, Tunables};
use wasmtime_jit::{native, CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::{
debug_builtins, InstanceHandle, RuntimeMemoryCreator, SignalHandler, SignatureRegistry,
StackMapRegistry, VMExternRef, VMExternRefActivationsTable, VMInterrupts,
VMSharedSignatureIndex,
};
#[derive(Clone)]
pub struct Config {
pub(crate) flags: settings::Builder,
pub(crate) isa_flags: isa::Builder,
pub(crate) tunables: Tunables,
pub(crate) strategy: CompilationStrategy,
#[cfg(feature = "cache")]
pub(crate) cache_config: CacheConfig,
pub(crate) profiler: Arc<dyn ProfilingAgent>,
pub(crate) memory_creator: Option<MemoryCreatorProxy>,
pub(crate) max_wasm_stack: usize,
wasm_threads: bool,
wasm_reference_types: bool,
pub(crate) wasm_bulk_memory: bool,
wasm_simd: bool,
wasm_multi_value: bool,
}
impl Config {
pub fn new() -> Config {
let mut tunables = Tunables::default();
if cfg!(windows) {
tunables.static_memory_bound = cmp::min(tunables.static_memory_bound, 0x100);
tunables.static_memory_offset_guard_size =
cmp::min(tunables.static_memory_offset_guard_size, 0x10000);
}
let mut flags = settings::builder();
flags
.enable("avoid_div_traps")
.expect("should be valid flag");
flags
.set("enable_verifier", "false")
.expect("should be valid flag");
flags
.set("opt_level", "speed")
.expect("should be valid flag");
flags
.set("enable_probestack", "false")
.expect("should be valid flag");
Config {
tunables,
flags,
isa_flags: native::builder(),
strategy: CompilationStrategy::Auto,
#[cfg(feature = "cache")]
cache_config: CacheConfig::new_cache_disabled(),
profiler: Arc::new(NullProfilerAgent),
memory_creator: None,
max_wasm_stack: 1 << 20,
wasm_threads: false,
wasm_reference_types: cfg!(target_arch = "x86_64"),
wasm_bulk_memory: true,
wasm_simd: false,
wasm_multi_value: true,
}
}
pub fn debug_info(&mut self, enable: bool) -> &mut Self {
self.tunables.debug_info = enable;
self
}
pub fn interruptable(&mut self, enable: bool) -> &mut Self {
self.tunables.interruptable = enable;
self
}
pub fn max_wasm_stack(&mut self, size: usize) -> &mut Self {
self.max_wasm_stack = size;
self
}
pub fn wasm_threads(&mut self, enable: bool) -> &mut Self {
self.wasm_threads = enable;
if enable {
self.wasm_bulk_memory(true);
}
self
}
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
self.wasm_reference_types = enable;
self.flags
.set("enable_safepoints", if enable { "true" } else { "false" })
.unwrap();
if enable {
self.wasm_bulk_memory(true);
}
self
}
pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
self.wasm_simd = enable;
let val = if enable { "true" } else { "false" };
self.flags
.set("enable_simd", val)
.expect("should be valid flag");
self
}
pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
self.wasm_bulk_memory = enable;
self
}
pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self {
self.wasm_multi_value = enable;
self
}
pub fn strategy(&mut self, strategy: Strategy) -> Result<&mut Self> {
self.strategy = match strategy {
Strategy::Auto => CompilationStrategy::Auto,
Strategy::Cranelift => CompilationStrategy::Cranelift,
#[cfg(feature = "lightbeam")]
Strategy::Lightbeam => CompilationStrategy::Lightbeam,
#[cfg(not(feature = "lightbeam"))]
Strategy::Lightbeam => {
anyhow::bail!("lightbeam compilation strategy wasn't enabled at compile time");
}
};
Ok(self)
}
pub fn profiler(&mut self, profile: ProfilingStrategy) -> Result<&mut Self> {
self.profiler = match profile {
ProfilingStrategy::JitDump => Arc::new(JitDumpAgent::new()?) as Arc<dyn ProfilingAgent>,
ProfilingStrategy::VTune => Arc::new(VTuneAgent::new()?) as Arc<dyn ProfilingAgent>,
ProfilingStrategy::None => Arc::new(NullProfilerAgent),
};
Ok(self)
}
pub fn cranelift_debug_verifier(&mut self, enable: bool) -> &mut Self {
let val = if enable { "true" } else { "false" };
self.flags
.set("enable_verifier", val)
.expect("should be valid flag");
self
}
pub fn cranelift_opt_level(&mut self, level: OptLevel) -> &mut Self {
let val = match level {
OptLevel::None => "none",
OptLevel::Speed => "speed",
OptLevel::SpeedAndSize => "speed_and_size",
};
self.flags
.set("opt_level", val)
.expect("should be valid flag");
self
}
pub fn cranelift_nan_canonicalization(&mut self, enable: bool) -> &mut Self {
let val = if enable { "true" } else { "false" };
self.flags
.set("enable_nan_canonicalization", val)
.expect("should be valid flag");
self
}
pub unsafe fn cranelift_other_flag(&mut self, name: &str, value: &str) -> Result<&mut Self> {
if let Err(err) = self.flags.set(name, value) {
match err {
SetError::BadName(_) => {
self.isa_flags.set(name, value)?;
}
_ => bail!(err),
}
}
Ok(self)
}
#[cfg(feature = "cache")]
pub fn cache_config_load(&mut self, path: impl AsRef<Path>) -> Result<&mut Self> {
self.cache_config = CacheConfig::from_file(Some(path.as_ref()))?;
Ok(self)
}
#[cfg(feature = "cache")]
pub fn cache_config_load_default(&mut self) -> Result<&mut Self> {
self.cache_config = CacheConfig::from_file(None)?;
Ok(self)
}
pub fn with_host_memory(&mut self, mem_creator: Arc<dyn MemoryCreator>) -> &mut Self {
self.memory_creator = Some(MemoryCreatorProxy { mem_creator });
self
}
pub fn static_memory_maximum_size(&mut self, max_size: u64) -> &mut Self {
let max_pages = max_size / u64::from(wasmtime_environ::WASM_PAGE_SIZE);
self.tunables.static_memory_bound = u32::try_from(max_pages).unwrap_or(u32::max_value());
self
}
pub fn static_memory_guard_size(&mut self, guard_size: u64) -> &mut Self {
let guard_size = round_up_to_pages(guard_size);
let guard_size = cmp::max(guard_size, self.tunables.dynamic_memory_offset_guard_size);
self.tunables.static_memory_offset_guard_size = guard_size;
self
}
pub fn dynamic_memory_guard_size(&mut self, guard_size: u64) -> &mut Self {
let guard_size = round_up_to_pages(guard_size);
self.tunables.dynamic_memory_offset_guard_size = guard_size;
self.tunables.static_memory_offset_guard_size =
cmp::max(guard_size, self.tunables.static_memory_offset_guard_size);
self
}
pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
self.isa_flags
.clone()
.finish(settings::Flags::new(self.flags.clone()))
}
pub(crate) fn target_isa_with_reference_types(&self) -> Box<dyn TargetIsa> {
let mut flags = self.flags.clone();
flags.set("enable_safepoints", "true").unwrap();
self.isa_flags.clone().finish(settings::Flags::new(flags))
}
pub(crate) fn validator(&self) -> Validator {
let mut ret = Validator::new();
ret.wasm_threads(self.wasm_threads)
.wasm_bulk_memory(self.wasm_bulk_memory)
.wasm_multi_value(self.wasm_multi_value)
.wasm_reference_types(self.wasm_reference_types)
.wasm_simd(self.wasm_simd);
return ret;
}
fn build_compiler(&self) -> Compiler {
let isa = self.target_isa();
Compiler::new(isa, self.strategy, self.tunables.clone())
}
pub(crate) fn compiler_fingerprint<H>(&self, state: &mut H)
where
H: Hasher,
{
self.flags.hash(state);
self.tunables.hash(state);
let triple = Triple::host();
triple.hash(state);
env!("CARGO_PKG_VERSION").hash(state);
}
}
fn round_up_to_pages(val: u64) -> u64 {
let page_size = region::page::size() as u64;
debug_assert!(page_size.is_power_of_two());
val.checked_add(page_size - 1)
.map(|val| val & !(page_size - 1))
.unwrap_or(u64::max_value() / page_size + 1)
}
impl Default for Config {
fn default() -> Config {
Config::new()
}
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Config")
.field("debug_info", &self.tunables.debug_info)
.field("strategy", &self.strategy)
.field("wasm_threads", &self.wasm_threads)
.field("wasm_reference_types", &self.wasm_reference_types)
.field("wasm_bulk_memory", &self.wasm_bulk_memory)
.field("wasm_simd", &self.wasm_simd)
.field("wasm_multi_value", &self.wasm_multi_value)
.field(
"flags",
&settings::Flags::new(self.flags.clone()).to_string(),
)
.finish()
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum Strategy {
Auto,
Cranelift,
Lightbeam,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum OptLevel {
None,
Speed,
SpeedAndSize,
}
#[derive(Debug, Clone, Copy)]
pub enum ProfilingStrategy {
None,
JitDump,
VTune,
}
#[derive(Clone)]
pub struct Engine {
inner: Arc<EngineInner>,
}
struct EngineInner {
config: Config,
compiler: Compiler,
}
impl Engine {
pub fn new(config: &Config) -> Engine {
debug_builtins::ensure_exported();
Engine {
inner: Arc::new(EngineInner {
config: config.clone(),
compiler: config.build_compiler(),
}),
}
}
pub fn config(&self) -> &Config {
&self.inner.config
}
pub(crate) fn compiler(&self) -> &Compiler {
&self.inner.compiler
}
#[cfg(feature = "cache")]
pub(crate) fn cache_config(&self) -> &CacheConfig {
&self.config().cache_config
}
pub fn same(a: &Engine, b: &Engine) -> bool {
Arc::ptr_eq(&a.inner, &b.inner)
}
}
impl Default for Engine {
fn default() -> Engine {
Engine::new(&Config::default())
}
}
#[derive(Clone)]
pub struct Store {
inner: Rc<StoreInner>,
}
pub(crate) struct StoreInner {
engine: Engine,
interrupts: Arc<VMInterrupts>,
signatures: RefCell<SignatureRegistry>,
instances: RefCell<Vec<InstanceHandle>>,
signal_handler: RefCell<Option<Box<SignalHandler<'static>>>>,
jit_code_ranges: RefCell<Vec<(usize, usize)>>,
externref_activations_table: VMExternRefActivationsTable,
stack_map_registry: StackMapRegistry,
}
struct HostInfoKey(VMExternRef);
impl PartialEq for HostInfoKey {
fn eq(&self, rhs: &Self) -> bool {
VMExternRef::eq(&self.0, &rhs.0)
}
}
impl Eq for HostInfoKey {}
impl Hash for HostInfoKey {
fn hash<H>(&self, hasher: &mut H)
where
H: Hasher,
{
VMExternRef::hash(&self.0, hasher);
}
}
impl Store {
pub fn new(engine: &Engine) -> Store {
wasmtime_runtime::init_traps();
Store {
inner: Rc::new(StoreInner {
engine: engine.clone(),
interrupts: Arc::new(Default::default()),
signatures: RefCell::new(Default::default()),
instances: RefCell::new(Vec::new()),
signal_handler: RefCell::new(None),
jit_code_ranges: RefCell::new(Vec::new()),
externref_activations_table: VMExternRefActivationsTable::new(),
stack_map_registry: StackMapRegistry::default(),
}),
}
}
pub(crate) fn from_inner(inner: Rc<StoreInner>) -> Store {
Store { inner }
}
pub fn engine(&self) -> &Engine {
&self.inner.engine
}
pub(crate) fn memory_creator(&self) -> Option<&dyn RuntimeMemoryCreator> {
self.engine()
.config()
.memory_creator
.as_ref()
.map(|x| x as _)
}
pub(crate) fn lookup_signature(&self, sig_index: VMSharedSignatureIndex) -> wasm::WasmFuncType {
self.inner
.signatures
.borrow()
.lookup_wasm(sig_index)
.expect("failed to lookup signature")
}
pub(crate) fn lookup_wasm_and_native_signatures(
&self,
sig_index: VMSharedSignatureIndex,
) -> (wasm::WasmFuncType, ir::Signature) {
self.inner
.signatures
.borrow()
.lookup_wasm_and_native_signatures(sig_index)
.expect("failed to lookup signature")
}
pub(crate) fn register_signature(
&self,
wasm_sig: wasm::WasmFuncType,
native: ir::Signature,
) -> VMSharedSignatureIndex {
self.inner
.signatures
.borrow_mut()
.register(wasm_sig, native)
}
pub(crate) fn signatures_mut(&self) -> std::cell::RefMut<'_, SignatureRegistry> {
self.inner.signatures.borrow_mut()
}
pub(crate) fn is_in_jit_code(&self, addr: usize) -> bool {
self.inner
.jit_code_ranges
.borrow()
.iter()
.any(|(start, end)| *start <= addr && addr < *end)
}
pub(crate) fn register_jit_code(&self, mut ranges: impl Iterator<Item = (usize, usize)>) {
match ranges.next() {
None => (),
Some(first) => {
if !self.is_in_jit_code(first.0) {
let mut jit_code_ranges = self.inner.jit_code_ranges.borrow_mut();
jit_code_ranges.push(first);
jit_code_ranges.extend(ranges);
}
}
}
}
pub(crate) fn register_stack_maps(&self, module: &Module) {
let module = &module.compiled_module();
self.stack_map_registry()
.register_stack_maps(module.stack_maps().map(|(func, stack_maps)| unsafe {
let ptr = (*func).as_ptr();
let len = (*func).len();
let start = ptr as usize;
let end = ptr as usize + len;
let range = start..end;
(range, stack_maps)
}));
}
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(handle.clone());
StoreInstanceHandle {
store: self.clone(),
handle,
}
}
pub(crate) fn existing_instance_handle(&self, handle: InstanceHandle) -> StoreInstanceHandle {
debug_assert!(self
.inner
.instances
.borrow()
.iter()
.any(|i| i.vmctx_ptr() == handle.vmctx_ptr()));
StoreInstanceHandle {
store: self.clone(),
handle,
}
}
pub(crate) fn weak(&self) -> Weak<StoreInner> {
Rc::downgrade(&self.inner)
}
pub(crate) fn upgrade(weak: &Weak<StoreInner>) -> Option<Self> {
let inner = weak.upgrade()?;
Some(Self { inner })
}
pub(crate) fn signal_handler(&self) -> std::cell::Ref<'_, Option<Box<SignalHandler<'static>>>> {
self.inner.signal_handler.borrow()
}
pub(crate) fn signal_handler_mut(
&self,
) -> std::cell::RefMut<'_, Option<Box<SignalHandler<'static>>>> {
self.inner.signal_handler.borrow_mut()
}
pub(crate) fn interrupts(&self) -> &Arc<VMInterrupts> {
&self.inner.interrupts
}
pub fn same(a: &Store, b: &Store) -> bool {
Rc::ptr_eq(&a.inner, &b.inner)
}
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
if self.engine().config().tunables.interruptable {
Ok(InterruptHandle {
interrupts: self.interrupts().clone(),
})
} else {
bail!("interrupts aren't enabled for this `Store`")
}
}
pub(crate) fn externref_activations_table(&self) -> &VMExternRefActivationsTable {
&self.inner.externref_activations_table
}
pub(crate) fn stack_map_registry(&self) -> &StackMapRegistry {
&self.inner.stack_map_registry
}
pub fn gc(&self) {
unsafe {
wasmtime_runtime::gc(
&self.inner.stack_map_registry,
&self.inner.externref_activations_table,
);
}
}
}
impl Default for Store {
fn default() -> Store {
Store::new(&Engine::default())
}
}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let inner = &*self.inner as *const StoreInner;
f.debug_struct("Store").field("inner", &inner).finish()
}
}
impl Drop for StoreInner {
fn drop(&mut self) {
for instance in self.instances.get_mut().iter() {
unsafe {
instance.dealloc();
}
}
}
}
pub struct InterruptHandle {
interrupts: Arc<VMInterrupts>,
}
impl InterruptHandle {
pub fn interrupt(&self) {
self.interrupts.interrupt()
}
}
fn _assert_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Engine>();
_assert::<Config>();
_assert::<InterruptHandle>();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Module;
use tempfile::TempDir;
#[test]
fn cache_accounts_for_opt_level() -> Result<()> {
let td = TempDir::new()?;
let config_path = td.path().join("config.toml");
std::fs::write(
&config_path,
&format!(
"
[cache]
enabled = true
directory = '{}'
",
td.path().join("cache").display()
),
)?;
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::None)
.cache_config_load(&config_path)?;
let engine = Engine::new(&cfg);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 0);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 1);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::Speed)
.cache_config_load(&config_path)?;
let engine = Engine::new(&cfg);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 0);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 1);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
.cache_config_load(&config_path)?;
let engine = Engine::new(&cfg);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 0);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 1);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
if !cfg!(target_arch = "aarch64") {
let mut cfg = Config::new();
cfg.debug_info(true).cache_config_load(&config_path)?;
let engine = Engine::new(&cfg);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 0);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(engine.config().cache_config.cache_hits(), 1);
assert_eq!(engine.config().cache_config.cache_misses(), 1);
}
Ok(())
}
}