diff --git a/Cargo.toml b/Cargo.toml index be37d8cec7a2..efdae9083e0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ vtune = ["wasmtime/vtune"] wasi-crypto = ["wasmtime-wasi-crypto"] wasi-nn = ["wasmtime-wasi-nn"] uffd = ["wasmtime/uffd"] +all-arch = ["wasmtime/all-arch"] # Try the experimental, work-in-progress new x86_64 backend. This is not stable # as of June 2020. diff --git a/RELEASES.md b/RELEASES.md index dd8ca5ddd221..b19e2b2105f9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,35 @@ -------------------------------------------------------------------------------- +## Unreleased + +### Added + +* The `wasmtime compile` command was added to support AOT compilation of Wasm + modules. + +* The `Module::compile` method was added to support AOT compilation of a module. + +* Added the `Config::cranelift_flag_enable` to enable setting Cranelift boolean + flags or presets in a config. + +### Changed + +* Breaking: the CLI option `--cranelift-flags` was changed to `--cranelift-flag`. + +* Breaking: the CLI option `--enable-reference-types=false` has been changed to + `--disable-reference-types` as it is enabled by default. + +* Breaking: the CLI option `--enable-multi-value=false` has been changed to + `--disable-multi-value` as it is enabled by default. + +* Breaking: the CLI option `--enable-bulk-memory=false` has been changed to + `--disable-bulk-memory` as it is enabled by default. + +* Modules serialized with `Module::serialize` can now be deserialized with + `Module::deserialize` on a compatible host that does not have to match the + original environment exactly. + ## 0.25.0 Released 2021-03-16. @@ -39,7 +68,7 @@ Released 2021-03-16. ### Fixed -* Interepretation of timestamps in `poll_oneoff` for WASI have been fixed to +* Interpretation of timestamps in `poll_oneoff` for WASI have been fixed to correctly use nanoseconds instead of microseconds. [#2717](https://github.com/bytecodealliance/wasmtime/pull/2717) diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 80a083250ed9..751f6316a2d1 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -53,3 +53,4 @@ wasm = ["wat", "cranelift-wasm"] experimental_x64 = ["cranelift-codegen/x64", "cranelift-filetests/experimental_x64", "cranelift-reader/experimental_x64"] experimental_arm32 = ["cranelift-codegen/arm32", "cranelift-filetests/experimental_arm32"] souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"] +all-arch = ["cranelift-codegen/all-arch"] diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index a70ddccfe18c..70b18726387e 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -70,6 +70,72 @@ fn gen_constructor(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatte fmtln!(fmt, "}"); } +/// Generates the `iter_enabled` function. +fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) { + fmtln!(fmt, "impl Flags {"); + fmt.indent(|fmt| { + fmt.doc_comment("Iterates the enabled boolean settings."); + fmtln!(fmt, "pub fn iter_enabled(&self) -> impl Iterator {"); + fmt.indent(|fmt| { + fmtln!(fmt, "let mut bytes = [0; {}];", group.settings_size); + fmtln!(fmt, "bytes.copy_from_slice(&self.bytes[0..{}]);", group.settings_size); + fmtln!(fmt, "DESCRIPTORS.iter().filter_map(move |d| {"); + fmt.indent(|fmt| { + fmtln!(fmt, "if match d.detail {"); + fmt.indent(|fmt| { + fmtln!(fmt, "detail::Detail::Bool { bit } => (bytes[d.offset as usize] & (1 << bit as usize)) != 0,"); + fmtln!(fmt, "_ => false"); + }); + fmtln!(fmt, "} {"); + fmt.indent(|fmt| { + fmtln!(fmt, "Some(d.name)"); + }); + fmtln!(fmt, "} else {"); + fmt.indent(|fmt| { + fmtln!(fmt, "None"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "})"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); +} + +/// Generates the `is_enabled` function. +fn gen_is_enabled(fmt: &mut Formatter) { + fmtln!(fmt, "impl Flags {"); + fmt.indent(|fmt| { + fmt.doc_comment("Checks if a boolean setting is enabled by name."); + fmtln!(fmt, "pub fn is_enabled(&self, name: &str) -> bool {"); + fmt.indent(|fmt| { + fmtln!(fmt, "match crate::constant_hash::probe(&TEMPLATE, name, crate::constant_hash::simple_hash(name)) {"); + fmt.indent(|fmt| { + fmtln!(fmt, "Err(_) => false,"); + fmtln!(fmt, "Ok(entry) => {"); + fmt.indent(|fmt| { + fmtln!(fmt, "let d = &TEMPLATE.descriptors[TEMPLATE.hash_table[entry] as usize];"); + fmtln!(fmt, "match &d.detail {"); + fmt.indent(|fmt| { + fmtln!(fmt, "detail::Detail::Bool{ bit } => {"); + fmt.indent(|fmt| { + fmtln!(fmt, "(self.bytes[d.offset as usize] & (1 << bit)) != 0"); + }); + fmtln!(fmt, "},"); + fmtln!(fmt, "_ => false"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); +} + /// Emit Display and FromStr implementations for enum settings. fn gen_to_and_from_str(name: &str, values: &[&'static str], fmt: &mut Formatter) { fmtln!(fmt, "impl fmt::Display for {} {{", name); @@ -427,6 +493,8 @@ fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) { fmtln!(fmt, "}"); gen_constructor(group, parent, fmt); + gen_iterator(group, fmt); + gen_is_enabled(fmt); gen_enum_types(group, fmt); gen_getters(group, fmt); gen_descriptors(group, fmt); diff --git a/cranelift/codegen/meta/src/isa/arm64/mod.rs b/cranelift/codegen/meta/src/isa/arm64/mod.rs index cbc21347e9f7..135096324285 100644 --- a/cranelift/codegen/meta/src/isa/arm64/mod.rs +++ b/cranelift/codegen/meta/src/isa/arm64/mod.rs @@ -8,6 +8,9 @@ use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder}; use crate::shared::Definitions as SharedDefinitions; fn define_settings(_shared: &SettingGroup) -> SettingGroup { + // Note: Wasmtime's `compile` command exposes these settings as CLI options + // If the settings change, please update src/commands/compile.rs to match. + let mut setting = SettingGroupBuilder::new("arm64"); let has_lse = setting.add_bool("has_lse", "Large System Extensions", false); diff --git a/cranelift/codegen/meta/src/isa/x86/settings.rs b/cranelift/codegen/meta/src/isa/x86/settings.rs index dddd69abb3ca..2540ac20657a 100644 --- a/cranelift/codegen/meta/src/isa/x86/settings.rs +++ b/cranelift/codegen/meta/src/isa/x86/settings.rs @@ -3,6 +3,9 @@ use crate::cdsl::settings::{PredicateNode, SettingGroup, SettingGroupBuilder}; pub(crate) fn define(shared: &SettingGroup) -> SettingGroup { let mut settings = SettingGroupBuilder::new("x86"); + // Note: Wasmtime's `compile` command exposes these settings as CLI options + // If the settings change, please update src/commands/compile.rs to match. + // CPUID.01H:ECX let has_sse3 = settings.add_bool("has_sse3", "SSE3: CPUID.01H:ECX.SSE3[bit 0]", false); let has_ssse3 = settings.add_bool("has_ssse3", "SSSE3: CPUID.01H:ECX.SSSE3[bit 9]", false); @@ -85,7 +88,7 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup { settings.add_predicate("use_lzcnt", predicate!(has_lzcnt)); // Some shared boolean values are used in x86 instruction predicates, so we need to group them - // in the same TargetIsa, for compabitibity with code generated by meta-python. + // in the same TargetIsa, for compatibility with code generated by meta-python. // TODO Once all the meta generation code has been migrated from Python to Rust, we can put it // back in the shared SettingGroup, and use it in x86 instruction predicates. diff --git a/cranelift/codegen/src/isa/aarch64/mod.rs b/cranelift/codegen/src/isa/aarch64/mod.rs index 42b47b645edd..9fbf7e60c5b9 100644 --- a/cranelift/codegen/src/isa/aarch64/mod.rs +++ b/cranelift/codegen/src/isa/aarch64/mod.rs @@ -7,10 +7,8 @@ use crate::isa::Builder as IsaBuilder; use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode}; use crate::result::CodegenResult; use crate::settings as shared_settings; - -use alloc::boxed::Box; +use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; use core::hash::{Hash, Hasher}; - use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Aarch64Architecture, Architecture, Triple}; @@ -104,6 +102,17 @@ impl MachBackend for AArch64Backend { &self.flags } + fn enabled_isa_flags(&self) -> Vec { + self.isa_flags + .iter_enabled() + .map(ToOwned::to_owned) + .collect() + } + + fn is_flag_enabled(&self, flag: &str) -> bool { + self.isa_flags.is_enabled(flag) + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.flags.hash(&mut hasher); self.isa_flags.hash(&mut hasher); diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index 5757b844d255..50e6f6f59c85 100644 --- a/cranelift/codegen/src/isa/arm32/mod.rs +++ b/cranelift/codegen/src/isa/arm32/mod.rs @@ -7,7 +7,7 @@ use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, use crate::result::CodegenResult; use crate::settings; -use alloc::boxed::Box; +use alloc::{boxed::Box, string::String, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Architecture, ArmArchitecture, Triple}; @@ -92,6 +92,14 @@ impl MachBackend for Arm32Backend { &self.flags } + fn enabled_isa_flags(&self) -> Vec { + Vec::new() + } + + fn is_flag_enabled(&self, _flag: &str) -> bool { + false + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.flags.hash(&mut hasher); } diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs index 94895b0b6e20..44fbf564d33a 100644 --- a/cranelift/codegen/src/isa/mod.rs +++ b/cranelift/codegen/src/isa/mod.rs @@ -63,8 +63,7 @@ use crate::result::CodegenResult; use crate::settings; use crate::settings::SetResult; use crate::timing; -use alloc::borrow::Cow; -use alloc::boxed::Box; +use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; use core::any::Any; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -265,8 +264,13 @@ pub trait TargetIsa: fmt::Display + Send + Sync { /// Get the ISA-independent flags that were used to make this trait object. fn flags(&self) -> &settings::Flags; - /// Hashes all flags, both ISA-independent and ISA-specific, into the - /// specified hasher. + /// Get the enabled ISA-dependent flags that were used to make this trait object. + fn enabled_isa_flags(&self) -> Vec; + + /// Determines if the given ISA-dependent flag is enabled. + fn is_flag_enabled(&self, flag: &str) -> bool; + + /// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher. fn hash_all_flags(&self, hasher: &mut dyn Hasher); /// Get the default calling convention of this target. diff --git a/cranelift/codegen/src/isa/riscv/mod.rs b/cranelift/codegen/src/isa/riscv/mod.rs index 500451c72eac..9db8b60ffa85 100644 --- a/cranelift/codegen/src/isa/riscv/mod.rs +++ b/cranelift/codegen/src/isa/riscv/mod.rs @@ -15,8 +15,12 @@ use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encoding use crate::isa::Builder as IsaBuilder; use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use crate::regalloc; -use alloc::borrow::Cow; -use alloc::boxed::Box; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + string::String, + vec::Vec, +}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -70,6 +74,17 @@ impl TargetIsa for Isa { &self.shared_flags } + fn enabled_isa_flags(&self) -> Vec { + self.isa_flags + .iter_enabled() + .map(ToOwned::to_owned) + .collect() + } + + fn is_flag_enabled(&self, flag: &str) -> bool { + self.isa_flags.is_enabled(flag) + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.shared_flags.hash(&mut hasher); self.isa_flags.hash(&mut hasher); diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs index da4065f2d0b4..ae8b889fbdaa 100644 --- a/cranelift/codegen/src/isa/x64/mod.rs +++ b/cranelift/codegen/src/isa/x64/mod.rs @@ -9,7 +9,7 @@ use crate::isa::Builder as IsaBuilder; use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode}; use crate::result::CodegenResult; use crate::settings::{self as shared_settings, Flags}; -use alloc::boxed::Box; +use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse, Reg}; use target_lexicon::Triple; @@ -85,6 +85,17 @@ impl MachBackend for X64Backend { &self.flags } + fn enabled_isa_flags(&self) -> Vec { + self.x64_flags + .iter_enabled() + .map(ToOwned::to_owned) + .collect() + } + + fn is_flag_enabled(&self, flag: &str) -> bool { + self.x64_flags.is_enabled(flag) + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.flags.hash(&mut hasher); self.x64_flags.hash(&mut hasher); diff --git a/cranelift/codegen/src/isa/x86/mod.rs b/cranelift/codegen/src/isa/x86/mod.rs index 272c3dfe5d1f..c01f8acf01df 100644 --- a/cranelift/codegen/src/isa/x86/mod.rs +++ b/cranelift/codegen/src/isa/x86/mod.rs @@ -21,8 +21,12 @@ use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use crate::regalloc; use crate::result::CodegenResult; use crate::timing; -use alloc::borrow::Cow; -use alloc::boxed::Box; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + string::String, + vec::Vec, +}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -79,6 +83,17 @@ impl TargetIsa for Isa { &self.shared_flags } + fn enabled_isa_flags(&self) -> Vec { + self.isa_flags + .iter_enabled() + .map(ToOwned::to_owned) + .collect() + } + + fn is_flag_enabled(&self, flag: &str) -> bool { + self.isa_flags.is_enabled(flag) + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.shared_flags.hash(&mut hasher); self.isa_flags.hash(&mut hasher); diff --git a/cranelift/codegen/src/machinst/adapter.rs b/cranelift/codegen/src/machinst/adapter.rs index eb4760fae57f..a75e7393f638 100644 --- a/cranelift/codegen/src/machinst/adapter.rs +++ b/cranelift/codegen/src/machinst/adapter.rs @@ -14,7 +14,6 @@ use crate::regalloc::RegDiversions; use crate::isa::unwind::systemv::RegisterMappingError; use core::any::Any; -use core::hash::Hasher; use std::borrow::Cow; use std::fmt; use target_lexicon::Triple; @@ -59,8 +58,16 @@ impl TargetIsa for TargetIsaAdapter { self.backend.flags() } + fn enabled_isa_flags(&self) -> Vec { + self.backend.enabled_isa_flags() + } + + fn is_flag_enabled(&self, flag: &str) -> bool { + self.backend.is_flag_enabled(flag) + } + fn hash_all_flags(&self, hasher: &mut dyn Hasher) { - self.backend.hash_all_flags(hasher) + self.backend.hash_all_flags(hasher); } fn register_info(&self) -> RegInfo { diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs index d7835a98f743..d1c96640dcb7 100644 --- a/cranelift/codegen/src/machinst/mod.rs +++ b/cranelift/codegen/src/machinst/mod.rs @@ -69,13 +69,13 @@ use crate::value_label::ValueLabelsRanges; use alloc::boxed::Box; use alloc::vec::Vec; use core::fmt::Debug; +use core::hash::Hasher; use cranelift_entity::PrimaryMap; use regalloc::RegUsageCollector; use regalloc::{ RealReg, RealRegUniverse, Reg, RegClass, RegUsageMapper, SpillSlot, VirtualReg, Writable, }; use smallvec::{smallvec, SmallVec}; -use std::hash::Hasher; use std::string::String; use target_lexicon::Triple; @@ -368,8 +368,13 @@ pub trait MachBackend { /// Return flags for this backend. fn flags(&self) -> &Flags; - /// Hashes all flags, both ISA-independent and ISA-specific, into the - /// specified hasher. + /// Get the enabled ISA-dependent flags that were used to make this trait object. + fn enabled_isa_flags(&self) -> Vec; + + /// Determines if the given ISA-dependent flag is enabled. + fn is_flag_enabled(&self, flag: &str) -> bool; + + /// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher. fn hash_all_flags(&self, hasher: &mut dyn Hasher); /// Return triple for this backend. diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index 07f2aedaeca9..bbe6c1e1b9aa 100644 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -10,7 +10,9 @@ pub mod ir { } pub mod settings { - pub use cranelift_codegen::settings::{builder, Builder, Configurable, Flags, SetError}; + pub use cranelift_codegen::settings::{ + builder, Builder, Configurable, Flags, OptLevel, SetError, + }; } pub mod isa { diff --git a/crates/environ/src/tunables.rs b/crates/environ/src/tunables.rs index 4e5aba91450e..8a786ae88b40 100644 --- a/crates/environ/src/tunables.rs +++ b/crates/environ/src/tunables.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; + /// Tunable parameters for WebAssembly compilation. -#[derive(Clone, Hash)] +#[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: u32, diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 0bf2f88e1a8f..b125d3a649b0 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -46,6 +46,7 @@ lightbeam = ["wasmtime-lightbeam"] jitdump = ["wasmtime-profiling/jitdump"] vtune = ["wasmtime-profiling/vtune"] parallel-compilation = ["rayon"] +all-arch = ["cranelift-codegen/all-arch"] # Try the experimental, work-in-progress new x86_64 backend. This is not stable # as of June 2020. diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 49a2d0ecd4c6..e22c6a46c22e 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -312,7 +312,7 @@ impl CodeMemory { } } - // Register all unwind entiries for functions and trampolines. + // Register all unwind entires for functions and trampolines. // TODO will `u32` type for start/len be enough for large code base. for i in unwind_info { match i { diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index fe94c27c02f6..6d2fc7caf27f 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -5,6 +5,7 @@ use crate::object::{build_object, ObjectUnwindInfo}; use object::write::Object; #[cfg(feature = "parallel-compilation")] use rayon::prelude::*; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; use std::mem; use wasmparser::WasmFeatures; @@ -18,7 +19,7 @@ use wasmtime_environ::{ }; /// Select which kind of compilation to use. -#[derive(Copy, Clone, Debug, Hash)] +#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq)] pub enum CompilationStrategy { /// Let Wasmtime pick the strategy. Auto, @@ -108,6 +109,11 @@ impl Compiler { self.isa.as_ref() } + /// Return the compiler's strategy. + pub fn strategy(&self) -> CompilationStrategy { + self.strategy + } + /// Return the target's frontend configuration settings. pub fn frontend_config(&self) -> TargetFrontendConfig { self.isa.frontend_config() diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index df2cd77c312a..45c57c532f6a 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -438,7 +438,7 @@ fn build_code_memory( isa: &dyn TargetIsa, obj: &[u8], module: &Module, - unwind_info: &Box<[ObjectUnwindInfo]>, + unwind_info: &[ObjectUnwindInfo], ) -> Result< ( CodeMemory, diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index b36e1afb041c..34481ee9e1ab 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -79,3 +79,6 @@ async = ["wasmtime-fiber", "wasmtime-runtime/async"] # Enables userfaultfd support in the runtime's pooling allocator when building on Linux uffd = ["wasmtime-runtime/uffd"] + +# Enables support for all architectures in JIT and the `wasmtime compile` CLI command. +all-arch = ["wasmtime-jit/all-arch"] diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 05296216f9b5..1fd5ee8b3ea0 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -2,6 +2,7 @@ use crate::memory::MemoryCreator; use crate::trampoline::MemoryCreatorProxy; use crate::{func::HostFunc, Caller, FuncType, IntoFunc, Trap, Val, WasmRet, WasmTy}; use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; use std::cmp; use std::collections::HashMap; use std::convert::TryFrom; @@ -391,6 +392,20 @@ impl Config { /// Creates a new configuration object with the default configuration /// specified. pub fn new() -> Self { + Self::new_with_isa_flags(native::builder()) + } + + /// Creates a [`Config`] for the given target triple. + /// + /// No CPU flags will be enabled for the config. + pub fn for_target(target: &str) -> Result { + use std::str::FromStr; + Ok(Self::new_with_isa_flags(native::lookup( + target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?, + )?)) + } + + fn new_with_isa_flags(isa_flags: isa::Builder) -> Self { let mut flags = settings::builder(); // There are two possible traps for division, and this way @@ -414,10 +429,15 @@ impl Config { .set("enable_probestack", "false") .expect("should be valid flag"); + // Reference types are enabled by default, so enable safepoints + flags + .set("enable_safepoints", "true") + .expect("should be valid flag"); + let mut ret = Self { tunables: Tunables::default(), flags, - isa_flags: native::builder(), + isa_flags, strategy: CompilationStrategy::Auto, #[cfg(feature = "cache")] cache_config: CacheConfig::new_cache_disabled(), @@ -898,6 +918,33 @@ impl Config { self } + /// Allows setting a Cranelift boolean flag or preset. This allows + /// fine-tuning of Cranelift settings. + /// + /// Since Cranelift flags may be unstable, this method should not be considered to be stable + /// either; other `Config` functions should be preferred for stability. + /// + /// # Safety + /// + /// This is marked as unsafe, because setting the wrong flag might break invariants, + /// resulting in execution hazards. + /// + /// # Errors + /// + /// This method can fail if the flag's name does not exist. + pub unsafe fn cranelift_flag_enable(&mut self, flag: &str) -> Result<&mut Self> { + if let Err(err) = self.flags.enable(flag) { + match err { + SetError::BadName(_) => { + // Try the target-specific flags. + self.isa_flags.enable(flag)?; + } + _ => bail!(err), + } + } + Ok(self) + } + /// Allows settings another Cranelift flag defined by a flag name and value. This allows /// fine-tuning of Cranelift settings. /// @@ -1419,7 +1466,7 @@ pub enum Strategy { /// Possible optimization levels for the Cranelift codegen backend. #[non_exhaustive] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub enum OptLevel { /// No optimizations performed, minimizes compilation time by disabling most /// optimizations. diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 066d034cbcff..d843b9db43b3 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -175,6 +175,10 @@ //! lock contention is hampering multithreading throughput. This feature is only //! supported on Linux and requires a Linux kernel version 4.11 or higher. //! +//! * `all-arch` - Not enabled by default. This feature compiles in support for +//! all architectures for both the JIT compiler and the `wasmtime compile` CLI +//! command. +//! //! ## Examples //! //! In addition to the examples below be sure to check out the [online embedding diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index b068416ee764..c9922b4f2797 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -2,9 +2,8 @@ use crate::types::{ExportType, ExternType, ImportType}; use crate::{Engine, ModuleType}; use anyhow::{bail, Context, Result}; use bincode::Options; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::hash::Hash; +use std::fs; +use std::io::Write; use std::path::Path; use std::sync::Arc; use wasmparser::Validator; @@ -14,6 +13,12 @@ use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::wasm::ModuleIndex; use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; +mod serialization; + +use serialization::SerializedModule; + +const COMPILED_MODULE_HEADER: &[u8] = b"\0aot"; + /// A compiled WebAssembly module, ready to be instantiated. /// /// A `Module` is a compiled in-memory representation of an input WebAssembly @@ -30,7 +35,7 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; /// compiling the original wasm module only once with a single [`Module`] /// instance. /// -/// The `Module` is threadsafe and safe to share accross threads. +/// The `Module` is thread-safe and safe to share across threads. /// /// ## Modules and `Clone` /// @@ -103,75 +108,26 @@ struct ModuleInner { types: Arc, } -/// A small helper struct which defines modules are serialized. -#[derive(serde::Serialize, serde::Deserialize)] -struct ModuleSerialized<'a> { - /// All compiled artifacts neeeded by this module, where the last entry in - /// this list is the artifacts for the module itself. - artifacts: Vec>, - /// Closed-over module values that are also needed for this module. - modules: Vec>, - /// The index into the list of type tables that are used for this module's - /// type tables. - type_tables: usize, -} - -// This is like `std::borrow::Cow` but it doesn't have a `Clone` bound on `T` -enum MyCow<'a, T> { - Borrowed(&'a T), - Owned(T), -} - -impl<'a, T> MyCow<'a, T> { - fn unwrap_owned(self) -> T { - match self { - MyCow::Owned(val) => val, - MyCow::Borrowed(_) => unreachable!(), - } - } -} - -impl<'a, T: Serialize> Serialize for MyCow<'a, T> { - fn serialize(&self, dst: S) -> Result - where - S: serde::ser::Serializer, - { - match self { - MyCow::Borrowed(val) => val.serialize(dst), - MyCow::Owned(val) => val.serialize(dst), - } - } -} - -impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> { - fn deserialize(src: D) -> Result - where - D: serde::de::Deserializer<'a>, - { - Ok(MyCow::Owned(T::deserialize(src)?)) - } -} - impl Module { /// Creates a new WebAssembly `Module` from the given in-memory `bytes`. /// - /// The `bytes` provided must be in one of two formats: + /// The `bytes` provided must be in one of three formats: /// - /// * It can be a [binary-encoded][binary] WebAssembly module. This - /// is always supported. - /// * It may also be a [text-encoded][text] instance of the WebAssembly - /// text format. This is only supported when the `wat` feature of this - /// crate is enabled. If this is supplied then the text format will be - /// parsed before validation. Note that the `wat` feature is enabled by - /// default. + /// * A [binary-encoded][binary] WebAssembly module. This is always supported. + /// * A [text-encoded][text] instance of the WebAssembly text format. + /// This is only supported when the `wat` feature of this crate is enabled. + /// If this is supplied then the text format will be parsed before validation. + /// Note that the `wat` feature is enabled by default. + /// * A module compiled with [`Module::compile`] or the `wasmtime compile` command. /// /// The data for the wasm module must be loaded in-memory if it's present /// elsewhere, for example on disk. This requires that the entire binary is /// loaded into memory all at once, this API does not support streaming /// compilation of a module. /// - /// The WebAssembly binary will be decoded and validated. It will also be - /// compiled according to the configuration of the provided `engine`. + /// If the module has not been already been compiled, the WebAssembly binary will + /// be decoded and validated. It will also be compiled according to the + /// configuration of the provided `engine`. /// /// # Errors /// @@ -184,7 +140,7 @@ impl Module { /// * Implementation-specific limits were exceeded with a valid binary (for /// example too many locals) /// * The wasm binary may use features that are not enabled in the - /// configuration of `enging` + /// configuration of `engine` /// * If the `wat` feature is enabled and the input is text, then it may be /// rejected if it fails to parse. /// @@ -220,9 +176,14 @@ impl Module { /// # } /// ``` pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { + let bytes = bytes.as_ref(); + if bytes.starts_with(COMPILED_MODULE_HEADER) { + return Self::deserialize(engine, &bytes[COMPILED_MODULE_HEADER.len()..]); + } + #[cfg(feature = "wat")] - let bytes = wat::parse_bytes(bytes.as_ref())?; - Module::from_binary(engine, bytes.as_ref()) + let bytes = wat::parse_bytes(bytes)?; + Self::from_binary(engine, &bytes) } /// Creates a new WebAssembly `Module` from the given in-memory `binary` @@ -230,7 +191,7 @@ impl Module { /// /// See [`Module::new`] for other details. pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result { - let mut module = Module::new(engine, bytes.as_ref())?; + let mut module = Self::new(engine, bytes.as_ref())?; Arc::get_mut(&mut Arc::get_mut(&mut module.inner).unwrap().module) .unwrap() .module_mut() @@ -268,21 +229,20 @@ impl Module { /// # } /// ``` pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { - #[cfg(feature = "wat")] - let wasm = wat::parse_file(file)?; - #[cfg(not(feature = "wat"))] - let wasm = std::fs::read(file)?; - Module::new(engine, &wasm) + Self::new( + engine, + &fs::read(file).with_context(|| "failed to read input file")?, + ) } /// Creates a new WebAssembly `Module` from the given in-memory `binary` /// data. /// /// This is similar to [`Module::new`] except that it requires that the - /// `binary` input is a WebAssembly binary, the text format is not supported - /// by this function. It's generally recommended to use [`Module::new`], - /// but if it's required to not support the text format this function can be - /// used instead. + /// `binary` input is a WebAssembly binary or a compiled module, the + /// text format is not supported by this function. It's generally + /// recommended to use [`Module::new`], but if it's required to not + /// support the text format this function can be used instead. /// /// # Examples /// @@ -307,6 +267,10 @@ impl Module { /// # } /// ``` pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { + if binary.starts_with(COMPILED_MODULE_HEADER) { + return Self::deserialize(engine, &binary[COMPILED_MODULE_HEADER.len()..]); + } + const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); cfg_if::cfg_if! { @@ -371,6 +335,43 @@ impl Module { Ok(()) } + /// Ahead-of-time (AOT) compiles a WebAssembly module. + /// + /// The `bytes` provided must be in one of two formats: + /// + /// * A [binary-encoded][binary] WebAssembly module. This is always supported. + /// * A [text-encoded][text] instance of the WebAssembly text format. + /// This is only supported when the `wat` feature of this crate is enabled. + /// If this is supplied then the text format will be parsed before validation. + /// Note that the `wat` feature is enabled by default. + /// + /// See [`Module::new`] for errors that may be returned by this function. + /// + /// [binary]: https://webassembly.github.io/spec/core/binary/index.html + /// [text]: https://webassembly.github.io/spec/core/text/index.html + pub fn compile(engine: &Engine, bytes: &[u8], mut output: impl Write) -> Result<()> { + const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); + + if bytes.starts_with(COMPILED_MODULE_HEADER) { + bail!("input is already a compiled module"); + } + + #[cfg(feature = "wat")] + let bytes = wat::parse_bytes(&bytes)?; + + let (_, artifacts, types) = + CompilationArtifacts::build(engine.compiler(), &bytes, USE_PAGED_MEM_INIT)?; + + // Write a header that marks this as a compiled module + output.write_all(COMPILED_MODULE_HEADER)?; + bincode_options().serialize_into( + output, + &SerializedModule::from_artifacts(engine.compiler(), &artifacts, &types), + )?; + + Ok(()) + } + /// Returns the type signature of this module. pub fn ty(&self) -> ModuleType { let mut sig = ModuleType::new(); @@ -388,48 +389,13 @@ impl Module { sig } - /// Serialize compilation artifacts to the buffer. See also `deseriaize`. + /// Serialize compilation artifacts to the buffer. See also `deserialize`. pub fn serialize(&self) -> Result> { - let mut pushed = HashMap::new(); - let mut tables = Vec::new(); - let module = self.serialized_module(&mut pushed, &mut tables); - let artifacts = (compiler_fingerprint(self.engine()), tables, module); - let buffer = bincode_options().serialize(&artifacts)?; + let mut buffer = Vec::new(); + bincode_options().serialize_into(&mut buffer, &SerializedModule::new(self))?; Ok(buffer) } - fn serialized_module<'a>( - &'a self, - type_tables_pushed: &mut HashMap, - type_tables: &mut Vec<&'a TypeTables>, - ) -> ModuleSerialized<'a> { - // Deduplicate `Arc` using our two parameters to ensure we - // serialize type tables as little as possible. - let ptr = Arc::as_ptr(self.types()); - let type_tables_idx = *type_tables_pushed.entry(ptr as usize).or_insert_with(|| { - type_tables.push(self.types()); - type_tables.len() - 1 - }); - ModuleSerialized { - artifacts: self - .inner - .artifact_upvars - .iter() - .map(|i| MyCow::Borrowed(i.compilation_artifacts())) - .chain(Some(MyCow::Borrowed( - self.compiled_module().compilation_artifacts(), - ))) - .collect(), - modules: self - .inner - .module_upvars - .iter() - .map(|i| i.serialized_module(type_tables_pushed, type_tables)) - .collect(), - type_tables: type_tables_idx, - } - } - /// Deserializes and creates a module from the compilation artifacts. /// The `serialize` saves the compilation artifacts along with the host /// fingerprint, which consists of target, compiler flags, and wasmtime @@ -437,49 +403,13 @@ impl Module { /// /// The method will fail if fingerprints of current host and serialized /// one are different. The method does not verify the serialized artifacts - /// for modifications or curruptions. All responsibily of signing and its + /// for modifications or corruptions. All responsibly of signing and its /// verification falls on the embedder. pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { - let (fingerprint, types, serialized) = bincode_options() - .deserialize::<(u64, Vec, _)>(serialized) - .context("Deserialize compilation artifacts")?; - - if fingerprint != compiler_fingerprint(engine) { - bail!("Incompatible compilation artifact"); - } - - let types = types.into_iter().map(Arc::new).collect::>(); - return mk(engine, &types, serialized); - - fn mk( - engine: &Engine, - types: &Vec>, - module: ModuleSerialized<'_>, - ) -> Result { - let mut artifacts = CompiledModule::from_artifacts_list( - module - .artifacts - .into_iter() - .map(|i| i.unwrap_owned()) - .collect(), - engine.compiler().isa(), - &*engine.config().profiler, - )?; - let inner = ModuleInner { - engine: engine.clone(), - types: types[module.type_tables].clone(), - module: artifacts.pop().unwrap(), - artifact_upvars: artifacts, - module_upvars: module - .modules - .into_iter() - .map(|m| mk(engine, types, m)) - .collect::>>()?, - }; - Ok(Module { - inner: Arc::new(inner), - }) - } + bincode_options() + .deserialize::>(serialized) + .context("Deserialize compilation artifacts")? + .into_module(engine) } /// Creates a submodule `Module` value from the specified parameters. @@ -493,7 +423,7 @@ impl Module { /// the upvars array in the submodule to be created, and each element of /// this array is an index into this module's upvar array. /// * `module_upvars` - similar to `artifact_upvars` this is a mapping of - /// how to create the e`module_upvars` of the submodule being created. + /// how to create the `module_upvars` of the submodule being created. /// Each entry in this array is either an index into this module's own /// module upvars array or it's an index into `modules`, the list of /// modules so far for the instance where this submodule is being @@ -775,13 +705,6 @@ fn bincode_options() -> impl Options { bincode::DefaultOptions::new().with_varint_encoding() } -fn compiler_fingerprint(engine: &Engine) -> u64 { - use std::hash::Hasher; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - engine.compiler().hash(&mut hasher); - hasher.finish() -} - fn _assert_send_sync() { fn _assert() {} _assert::(); diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs new file mode 100644 index 000000000000..9c469924ad86 --- /dev/null +++ b/crates/wasmtime/src/module/serialization.rs @@ -0,0 +1,715 @@ +//! Implements module serialization. + +use super::ModuleInner; +use crate::{Engine, Module, OptLevel}; +use anyhow::{anyhow, bail, Result}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; +use std::sync::Arc; +use wasmtime_environ::Tunables; +use wasmtime_environ::{isa::TargetIsa, settings}; +use wasmtime_jit::{ + CompilationArtifacts, CompilationStrategy, CompiledModule, Compiler, TypeTables, +}; + +// This exists because `wasmparser::WasmFeatures` isn't serializable +#[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)] +struct WasmFeatures { + pub reference_types: bool, + pub multi_value: bool, + pub bulk_memory: bool, + pub module_linking: bool, + pub simd: bool, + pub threads: bool, + pub tail_call: bool, + pub deterministic_only: bool, + pub multi_memory: bool, + pub exceptions: bool, + pub memory64: bool, +} + +impl From<&wasmparser::WasmFeatures> for WasmFeatures { + fn from(other: &wasmparser::WasmFeatures) -> Self { + Self { + reference_types: other.reference_types, + multi_value: other.multi_value, + bulk_memory: other.bulk_memory, + module_linking: other.module_linking, + simd: other.simd, + threads: other.threads, + tail_call: other.tail_call, + deterministic_only: other.deterministic_only, + multi_memory: other.multi_memory, + exceptions: other.exceptions, + memory64: other.memory64, + } + } +} + +// This is like `std::borrow::Cow` but it doesn't have a `Clone` bound on `T` +enum MyCow<'a, T> { + Borrowed(&'a T), + Owned(T), +} + +impl<'a, T> MyCow<'a, T> { + fn unwrap_owned(self) -> T { + match self { + MyCow::Owned(val) => val, + MyCow::Borrowed(_) => unreachable!(), + } + } +} + +impl<'a, T: Serialize> Serialize for MyCow<'a, T> { + fn serialize(&self, dst: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + MyCow::Borrowed(val) => val.serialize(dst), + MyCow::Owned(val) => val.serialize(dst), + } + } +} + +impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> { + fn deserialize(src: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + Ok(MyCow::Owned(T::deserialize(src)?)) + } +} + +impl From for OptLevel { + fn from(level: settings::OptLevel) -> Self { + match level { + settings::OptLevel::Speed => OptLevel::Speed, + settings::OptLevel::SpeedAndSize => OptLevel::SpeedAndSize, + settings::OptLevel::None => OptLevel::None, + } + } +} + +/// A small helper struct which defines modules are serialized. +#[derive(Serialize, Deserialize)] +struct SerializedModuleData<'a> { + /// All compiled artifacts needed by this module, where the last entry in + /// this list is the artifacts for the module itself. + artifacts: Vec>, + /// Closed-over module values that are also needed for this module. + modules: Vec>, + /// The index into the list of type tables that are used for this module's + /// type tables. + type_tables: usize, +} + +impl<'a> SerializedModuleData<'a> { + pub fn new(module: &'a Module) -> (Self, Vec>) { + let mut pushed = HashMap::new(); + let mut tables = Vec::new(); + return (module_data(module, &mut pushed, &mut tables), tables); + + fn module_data<'a>( + module: &'a Module, + type_tables_pushed: &mut HashMap, + type_tables: &mut Vec>, + ) -> SerializedModuleData<'a> { + // Deduplicate `Arc` using our two parameters to ensure we + // serialize type tables as little as possible. + let ptr = Arc::as_ptr(module.types()); + let type_tables_idx = *type_tables_pushed.entry(ptr as usize).or_insert_with(|| { + type_tables.push(MyCow::Borrowed(module.types())); + type_tables.len() - 1 + }); + SerializedModuleData { + artifacts: module + .inner + .artifact_upvars + .iter() + .map(|i| MyCow::Borrowed(i.compilation_artifacts())) + .chain(Some(MyCow::Borrowed( + module.compiled_module().compilation_artifacts(), + ))) + .collect(), + modules: module + .inner + .module_upvars + .iter() + .map(|i| module_data(i, type_tables_pushed, type_tables)) + .collect(), + type_tables: type_tables_idx, + } + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct SerializedModule<'a> { + version: String, + target: String, + flags_hash: u64, + // Record the opt level as it is the most common Cranelift flag users might change + opt_level: OptLevel, + isa_flags: Vec, + strategy: CompilationStrategy, + tunables: Tunables, + features: WasmFeatures, + data: SerializedModuleData<'a>, + tables: Vec>, +} + +impl<'a> SerializedModule<'a> { + pub fn new(module: &'a Module) -> Self { + let (data, tables) = SerializedModuleData::new(module); + Self::with_data(module.engine().compiler(), data, tables) + } + + pub fn from_artifacts( + compiler: &Compiler, + artifacts: &'a Vec, + types: &'a TypeTables, + ) -> Self { + Self::with_data( + compiler, + SerializedModuleData { + artifacts: artifacts.iter().map(MyCow::Borrowed).collect(), + modules: Vec::new(), + type_tables: 0, + }, + vec![MyCow::Borrowed(types)], + ) + } + + fn with_data( + compiler: &Compiler, + data: SerializedModuleData<'a>, + tables: Vec>, + ) -> Self { + let isa = compiler.isa(); + + Self { + version: env!("CARGO_PKG_VERSION").to_string(), + target: isa.triple().to_string(), + opt_level: isa.flags().opt_level().into(), + flags_hash: Self::simple_hash(isa.flags()), + isa_flags: isa.enabled_isa_flags(), + strategy: compiler.strategy(), + tunables: compiler.tunables().clone(), + features: compiler.features().into(), + data, + tables, + } + } + + pub fn into_module(self, engine: &Engine) -> Result { + let compiler = engine.compiler(); + let isa = compiler.isa(); + + self.check_version()?; + self.check_triple(isa)?; + self.check_isa_flags(isa)?; + self.check_strategy(compiler)?; + self.check_tunables(compiler)?; + self.check_features(compiler)?; + + // Check the flags last as they are the least helpful in terms of diagnostic message + self.check_flags(isa)?; + + let types = self + .tables + .into_iter() + .map(|t| Arc::new(t.unwrap_owned())) + .collect::>(); + let module = mk(engine, &types, self.data)?; + + // Validate the module can be used with the current allocator + engine.allocator().validate(module.inner.module.module())?; + + return Ok(module); + + fn mk( + engine: &Engine, + types: &Vec>, + data: SerializedModuleData<'_>, + ) -> Result { + let mut artifacts = CompiledModule::from_artifacts_list( + data.artifacts + .into_iter() + .map(|i| i.unwrap_owned()) + .collect(), + engine.compiler().isa(), + &*engine.config().profiler, + )?; + let inner = ModuleInner { + engine: engine.clone(), + types: types[data.type_tables].clone(), + module: artifacts.pop().unwrap(), + artifact_upvars: artifacts, + module_upvars: data + .modules + .into_iter() + .map(|m| mk(engine, types, m)) + .collect::>>()?, + }; + + Ok(Module { + inner: Arc::new(inner), + }) + } + } + + fn check_version(&self) -> Result<()> { + if self.version != env!("CARGO_PKG_VERSION") { + bail!( + "Module was compiled with Wasmtime version '{}'", + self.version + ); + } + + Ok(()) + } + + fn check_triple(&self, isa: &dyn TargetIsa) -> Result<()> { + let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; + + if triple.architecture != isa.triple().architecture { + bail!( + "Module was compiled for architecture '{}'", + triple.architecture + ); + } + + if triple.operating_system != isa.triple().operating_system { + bail!( + "Module was compiled for operating system '{}'", + triple.operating_system + ); + } + + Ok(()) + } + + fn check_flags(&self, isa: &dyn TargetIsa) -> Result<()> { + let host_level = isa.flags().opt_level().into(); + if self.opt_level != host_level { + bail!("Module was compiled with optimization level '{:?}' but '{:?}' is expected for the host", self.opt_level, host_level); + } + + if self.flags_hash != Self::simple_hash(isa.flags()) { + bail!("Module was compiled with different Cranelift flags than the host"); + } + + Ok(()) + } + + fn check_isa_flags(&self, isa: &dyn TargetIsa) -> Result<()> { + for flag in &self.isa_flags { + if !isa.is_flag_enabled(flag) { + bail!("Host is missing CPU flag '{}'", flag); + } + } + + Ok(()) + } + + fn check_strategy(&self, compiler: &Compiler) -> Result<()> { + #[allow(unreachable_patterns)] + let matches = match (self.strategy, compiler.strategy()) { + (CompilationStrategy::Auto, CompilationStrategy::Auto) + | (CompilationStrategy::Auto, CompilationStrategy::Cranelift) + | (CompilationStrategy::Cranelift, CompilationStrategy::Auto) + | (CompilationStrategy::Cranelift, CompilationStrategy::Cranelift) => true, + #[cfg(feature = "lightbeam")] + (CompilationStrategy::Lightbeam, CompilationStrategy::Lightbeam) => true, + _ => false, + }; + + if !matches { + bail!("Module was compiled with strategy '{:?}'", self.strategy); + } + + Ok(()) + } + + fn check_int(found: T, expected: T, feature: &str) -> Result<()> { + if found == expected { + return Ok(()); + } + + bail!( + "Module was compiled with a {} of '{}' but '{}' is expected for the host", + feature, + found, + expected + ); + } + + fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> { + if found == expected { + return Ok(()); + } + + bail!( + "Module was compiled {} {} but it {} enabled for the host", + if found { "with" } else { "without" }, + feature, + if expected { "is" } else { "is not" } + ); + } + + fn simple_hash(v: T) -> u64 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + v.hash(&mut hasher); + hasher.finish() + } + + fn check_tunables(&self, compiler: &Compiler) -> Result<()> { + let other = compiler.tunables(); + + Self::check_int( + self.tunables.static_memory_bound, + other.static_memory_bound, + "static memory bound", + )?; + Self::check_int( + self.tunables.static_memory_offset_guard_size, + other.static_memory_offset_guard_size, + "static memory guard size", + )?; + Self::check_int( + self.tunables.dynamic_memory_offset_guard_size, + other.dynamic_memory_offset_guard_size, + "dynamic memory guard size", + )?; + Self::check_bool( + self.tunables.generate_native_debuginfo, + other.generate_native_debuginfo, + "debug information support", + )?; + Self::check_bool( + self.tunables.parse_wasm_debuginfo, + other.parse_wasm_debuginfo, + "WebAssembly backtrace support", + )?; + Self::check_bool( + self.tunables.interruptable, + other.interruptable, + "interruption support", + )?; + Self::check_bool( + self.tunables.consume_fuel, + other.consume_fuel, + "fuel support", + )?; + Self::check_bool( + self.tunables.static_memory_bound_is_maximum, + other.static_memory_bound_is_maximum, + "pooling allocation support", + )?; + + // At this point, the hashes should match (if not we're missing a check) + assert_eq!( + Self::simple_hash(&self.tunables), + Self::simple_hash(other), + "unexpected hash difference" + ); + + Ok(()) + } + + fn check_features(&self, compiler: &Compiler) -> Result<()> { + let other = compiler.features(); + Self::check_bool( + self.features.reference_types, + other.reference_types, + "WebAssembly reference types support", + )?; + Self::check_bool( + self.features.multi_value, + other.multi_value, + "WebAssembly multi-value support", + )?; + Self::check_bool( + self.features.bulk_memory, + other.bulk_memory, + "WebAssembly bulk memory support", + )?; + Self::check_bool( + self.features.module_linking, + other.module_linking, + "WebAssembly module linking support", + )?; + Self::check_bool(self.features.simd, other.simd, "WebAssembly SIMD support")?; + Self::check_bool( + self.features.threads, + other.threads, + "WebAssembly threads support", + )?; + Self::check_bool( + self.features.tail_call, + other.tail_call, + "WebAssembly tail-call support", + )?; + Self::check_bool( + self.features.deterministic_only, + other.deterministic_only, + "WebAssembly deterministic-only support", + )?; + Self::check_bool( + self.features.multi_memory, + other.multi_memory, + "WebAssembly multi-memory support", + )?; + Self::check_bool( + self.features.exceptions, + other.exceptions, + "WebAssembly exceptions support", + )?; + Self::check_bool( + self.features.memory64, + other.memory64, + "WebAssembly 64-bit memory support", + )?; + + // At this point, the hashes should match (if not we're missing a check) + assert_eq!( + Self::simple_hash(&self.features), + Self::simple_hash(other), + "unexpected hash difference" + ); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Config; + + #[test] + fn test_version_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.version = "0.0.1".to_string(); + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with Wasmtime version '0.0.1'" + ), + } + + Ok(()) + } + + #[test] + fn test_architecture_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.target = "unknown-generic-linux".to_string(); + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled for architecture 'unknown'", + ), + } + + Ok(()) + } + + #[test] + fn test_os_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.target = format!( + "{}-generic-unknown", + target_lexicon::Triple::host().architecture + ); + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled for operating system 'unknown'", + ), + } + + Ok(()) + } + + #[test] + fn test_opt_level_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.opt_level = OptLevel::None; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with optimization level 'None' but 'Speed' is expected for the host", + ), + } + + Ok(()) + } + + #[test] + fn test_cranelift_flags_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.flags_hash += 1; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with different Cranelift flags than the host", + ), + } + + Ok(()) + } + + #[test] + fn test_isa_flags_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.isa_flags.push("not_a_flag".to_string()); + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Host is missing CPU flag 'not_a_flag'",), + } + + Ok(()) + } + + #[cfg(feature = "lightbeam")] + #[test] + fn test_compilation_strategy_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.strategy = CompilationStrategy::Lightbeam; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with strategy 'Cranelift'", + ), + } + + Ok(()) + } + + #[test] + fn test_tunables_int_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.static_memory_offset_guard_size = 0; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"), + } + + Ok(()) + } + + #[test] + fn test_tunables_bool_mismatch() -> Result<()> { + let mut config = Config::new(); + config.interruptable(true); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.interruptable = false; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled without interruption support but it is enabled for the host" + ), + } + + let mut config = Config::new(); + config.interruptable(false); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.interruptable = true; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with interruption support but it is not enabled for the host" + ), + } + + Ok(()) + } + + #[test] + fn test_feature_mismatch() -> Result<()> { + let mut config = Config::new(); + config.wasm_simd(true); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.features.simd = false; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly SIMD support but it is enabled for the host"), + } + + let mut config = Config::new(); + config.wasm_simd(false); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.features.simd = true; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly SIMD support but it is not enabled for the host"), + } + + Ok(()) + } +} diff --git a/docs/cli-options.md b/docs/cli-options.md index d1204314ab7c..5701cb48a6be 100644 --- a/docs/cli-options.md +++ b/docs/cli-options.md @@ -80,3 +80,18 @@ with: ```sh $ wasmtime wasm2obj foo.wasm foo.o ``` + +## `compile` + +This subcommand is used to Ahead-Of-Time (AOT) compile a WebAssembly module to produce +a "compiled wasm" (.cwasm) file. + +The `wasmtime run` subcommand can then be used to run a AOT-compiled WebAssembly module: + +```sh +$ wasmtime compile foo.wasm +$ wasmtime foo.cwasm +``` + +AOT-compiled modules can be run from hosts that are compatible with the target +environment of the AOT-completed module. diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 89c1078f4816..8b7c67caad0e 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -6,7 +6,7 @@ use anyhow::Result; use structopt::{clap::AppSettings, clap::ErrorKind, StructOpt}; use wasmtime_cli::commands::{ - ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP, + CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP, }; /// Wasmtime WebAssembly Runtime @@ -38,6 +38,8 @@ enum WasmtimeApp { // !!! IMPORTANT: if subcommands are added or removed, update `parse_module` in `src/commands/run.rs`. !!! /// Controls Wasmtime configuration settings Config(ConfigCommand), + /// Compiles a WebAssembly module. + Compile(CompileCommand), /// Runs a WebAssembly module Run(RunCommand), /// Translates a WebAssembly module to native object file @@ -49,9 +51,10 @@ enum WasmtimeApp { impl WasmtimeApp { /// Executes the command. - pub fn execute(&self) -> Result<()> { + pub fn execute(self) -> Result<()> { match self { Self::Config(c) => c.execute(), + Self::Compile(c) => c.execute(), Self::Run(c) => c.execute(), Self::WasmToObj(c) => c.execute(), Self::Wast(c) => c.execute(), diff --git a/src/commands.rs b/src/commands.rs index e9891bab99a9..1f9bc601f9ce 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,8 +1,9 @@ //! The module for the Wasmtime CLI commands. +mod compile; mod config; mod run; mod wasm2obj; mod wast; -pub use self::{config::*, run::*, wasm2obj::*, wast::*}; +pub use self::{compile::*, config::*, run::*, wasm2obj::*, wast::*}; diff --git a/src/commands/compile.rs b/src/commands/compile.rs new file mode 100644 index 000000000000..79ea0305326c --- /dev/null +++ b/src/commands/compile.rs @@ -0,0 +1,413 @@ +//! The module that implements the `wasmtime wast` command. + +use crate::{init_file_per_thread_logger, CommonOptions}; +use anyhow::{anyhow, bail, Context, Result}; +use std::fs::{self, File}; +use std::io::BufWriter; +use std::path::PathBuf; +use structopt::{ + clap::{AppSettings, ArgGroup}, + StructOpt, +}; +use target_lexicon::Triple; +use wasmtime::{Config, Engine, Module}; + +/// Compiles a WebAssembly module. +#[derive(StructOpt)] +#[structopt( + name = "compile", + version = env!("CARGO_PKG_VERSION"), + setting = AppSettings::ColoredHelp, + group = ArgGroup::with_name("x64").multiple(true), + group = ArgGroup::with_name("preset-x64"), + group = ArgGroup::with_name("aarch64").multiple(true).conflicts_with_all(&["x64", "preset-x64"]), + group = ArgGroup::with_name("preset-aarch64").conflicts_with_all(&["x64", "preset-x64"]), + after_help = "By default, no CPU flags will be enabled for the compilation.\n\ + \n\ + Use the various preset and CPU flag options for the environment being targeted.\n\ + \n\ + Usage examples:\n\ + \n\ + Compiling a WebAssembly module for the current platform:\n\ + \n \ + wasmtime compile example.wasm + \n\ + Specifying the output file:\n\ + \n \ + wasmtime compile -o output.cwasm input.wasm\n\ + \n\ + Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ + \n \ + wasmtime compile --target x86_64-unknown-linux --skylake foo.wasm\n" +)] +pub struct CompileCommand { + #[structopt(flatten)] + common: CommonOptions, + + /// Enable support for interrupting WebAssembly code. + #[structopt(long)] + interruptable: bool, + + /// Enable SSE3 support (for x86-64 targets). + #[structopt(long, group = "x64")] + sse3: bool, + + /// Enable SSSE3 support (for x86-64 targets). + #[structopt(long, group = "x64")] + ssse3: bool, + + /// Enable SSE41 support (for x86-64 targets). + #[structopt(long, group = "x64")] + sse41: bool, + + /// Enable SSE42 support (for x86-64 targets). + #[structopt(long, group = "x64")] + sse42: bool, + + /// Enable AVX support (for x86-64 targets). + #[structopt(long, group = "x64")] + avx: bool, + + /// Enable AVX2 support (for x86-64 targets). + #[structopt(long, group = "x64")] + avx2: bool, + + /// Enable AVX512DQ support (for x86-64 targets). + #[structopt(long, group = "x64")] + avx512dq: bool, + + /// Enable AVX512VL support (for x86-64 targets). + #[structopt(long, group = "x64")] + avx512vl: bool, + + /// Enable AVX512F support (for x86-64 targets). + #[structopt(long, group = "x64")] + avx512f: bool, + + /// Enable POPCNT support (for x86-64 targets). + #[structopt(long, group = "x64")] + popcnt: bool, + + /// Enable BMI1 support (for x86-64 targets). + #[structopt(long, group = "x64")] + bmi1: bool, + + /// Enable BMI2 support (for x86-64 targets). + #[structopt(long, group = "x64")] + bmi2: bool, + + /// Enable LZCNT support (for x86-64 targets). + #[structopt(long, group = "x64")] + lzcnt: bool, + + /// Enable LSE support (for aarch64 targets). + #[structopt(long, group = "aarch64")] + lse: bool, + + /// Enable Nehalem preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + nehalem: bool, + + /// Enable Haswell preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + haswell: bool, + + /// Enable Broadwell preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + broadwell: bool, + + /// Enable Skylake preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + skylake: bool, + + /// Enable Cannonlake preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + cannonlake: bool, + + /// Enable Icelake preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + icelake: bool, + + /// Enable Zen preset (for x86-64 targets). + #[structopt(long, group = "x64", group = "preset-x64")] + znver1: bool, + + /// The target triple; default is the host triple + #[structopt(long, value_name = "TARGET")] + target: Option, + + /// The path of the output compiled module; defaults to .cwasm + #[structopt(short = "o", long, value_name = "OUTPUT", parse(from_os_str))] + output: Option, + + /// The path of the WebAssembly to compile + #[structopt(index = 1, value_name = "MODULE", parse(from_os_str))] + module: PathBuf, +} + +impl CompileCommand { + /// Executes the command. + pub fn execute(mut self) -> Result<()> { + if !self.common.disable_logging { + if self.common.log_to_files { + let prefix = "wasmtime.dbg."; + init_file_per_thread_logger(prefix); + } else { + pretty_env_logger::init(); + } + } + + let target = self + .target + .take() + .unwrap_or_else(|| Triple::host().to_string()); + + let mut config = self.common.config(Some(&target))?; + config.interruptable(self.interruptable); + + self.set_flags(&mut config, &target)?; + + let engine = Engine::new(&config)?; + + if self.module.file_name().is_none() { + bail!( + "'{}' is not a valid input module path", + self.module.display() + ); + } + + let input = fs::read(&self.module).with_context(|| "failed to read input file")?; + + let output = self.output.take().unwrap_or_else(|| { + let mut output: PathBuf = self.module.file_name().unwrap().into(); + output.set_extension("cwasm"); + output + }); + + let mut writer = BufWriter::new(File::create(&output)?); + Module::compile(&engine, &input, &mut writer)?; + + Ok(()) + } + + fn set_flags(&self, c: &mut Config, target: &str) -> Result<()> { + use std::str::FromStr; + + macro_rules! set_flag { + ($config:expr, $arch:expr, $flag:expr, $name:literal, $display:literal) => { + if $flag { + unsafe { + $config.cranelift_flag_enable($name).map_err(|_| { + anyhow!("{} is not supported for architecture '{}'", $display, $arch) + })?; + } + } + }; + } + + let arch = Triple::from_str(target).unwrap().architecture; + + set_flag!(c, arch, self.sse3, "has_sse3", "SSE3"); + set_flag!(c, arch, self.ssse3, "has_ssse3", "SSSE3"); + set_flag!(c, arch, self.sse41, "has_sse41", "SSE41"); + set_flag!(c, arch, self.sse42, "has_sse42", "SSE42"); + set_flag!(c, arch, self.avx, "has_avx", "AVX"); + set_flag!(c, arch, self.avx2, "has_avx2", "AVX2"); + set_flag!(c, arch, self.avx512dq, "has_avx512dq", "AVX512DQ"); + set_flag!(c, arch, self.avx512vl, "has_avx512vl", "AVX512VL"); + set_flag!(c, arch, self.avx512f, "has_avx512f", "AVX512F"); + set_flag!(c, arch, self.popcnt, "has_popcnt", "POPCNT"); + set_flag!(c, arch, self.bmi1, "has_bmi1", "BMI1"); + set_flag!(c, arch, self.bmi2, "has_bmi2", "BMI2"); + set_flag!(c, arch, self.lzcnt, "has_lzcnt", "LZCNT"); + set_flag!(c, arch, self.lse, "has_lse", "LSE"); + set_flag!(c, arch, self.nehalem, "nehalem", "Nehalem preset"); + set_flag!(c, arch, self.haswell, "haswell", "Haswell preset"); + set_flag!(c, arch, self.broadwell, "broadwell", "Broadwell preset"); + set_flag!(c, arch, self.skylake, "skylake", "Skylake preset"); + set_flag!(c, arch, self.cannonlake, "cannonlake", "Cannonlake preset"); + set_flag!(c, arch, self.icelake, "icelake", "Icelake preset"); + set_flag!(c, arch, self.znver1, "znver1", "Zen preset"); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + use tempfile::NamedTempFile; + use wasmtime::{Instance, Store}; + + #[test] + fn test_successful_compile() -> Result<()> { + let (mut input, input_path) = NamedTempFile::new()?.into_parts(); + input.write_all( + "(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(), + )?; + drop(input); + + let output_path = NamedTempFile::new()?.into_temp_path(); + + let command = CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + + let engine = Engine::default(); + let module = Module::from_file(&engine, output_path)?; + let store = Store::new(&engine); + let instance = Instance::new(&store, &module, &[])?; + let f = instance.get_typed_func::("f")?; + assert_eq!(f.call(1234).unwrap(), 1234); + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + #[test] + fn test_x64_flags_compile() -> Result<()> { + let (mut input, input_path) = NamedTempFile::new()?.into_parts(); + input.write_all("(module)".as_bytes())?; + drop(input); + + let output_path = NamedTempFile::new()?.into_temp_path(); + + // Set all the x64 flags to make sure they work + let command = CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "--sse3", + "--ssse3", + "--sse41", + "--sse42", + "--avx", + "--avx2", + "--avx512dq", + "--avx512vl", + "--avx512f", + "--popcnt", + "--bmi1", + "--bmi2", + "--lzcnt", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + + Ok(()) + } + + #[cfg(target_arch = "aarch64")] + #[test] + fn test_aarch64_flags_compile() -> Result<()> { + let (mut input, input_path) = NamedTempFile::new()?.into_parts(); + input.write_all("(module)".as_bytes())?; + drop(input); + + let output_path = NamedTempFile::new()?.into_temp_path(); + + // Set all the aarch64 flags to make sure they work + let command = CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "--lse", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + #[test] + fn test_incompatible_flags_compile() -> Result<()> { + let (mut input, input_path) = NamedTempFile::new()?.into_parts(); + input.write_all("(module)".as_bytes())?; + drop(input); + + let output_path = NamedTempFile::new()?.into_temp_path(); + + // x64 and aarch64 flags should conflict + match CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "--sse3", + "--lse", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ]) { + Ok(_) => unreachable!(), + Err(e) => { + assert!(e + .to_string() + .contains("cannot be used with one or more of the other specified arguments")); + } + } + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + #[test] + fn test_x64_presets_compile() -> Result<()> { + let (mut input, input_path) = NamedTempFile::new()?.into_parts(); + input.write_all("(module)".as_bytes())?; + drop(input); + + let output_path = NamedTempFile::new()?.into_temp_path(); + + for preset in &[ + "--nehalem", + "--haswell", + "--broadwell", + "--skylake", + "--cannonlake", + "--icelake", + "--znver1", + ] { + let command = CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + preset, + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + } + + // Two presets should conflict + match CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "--broadwell", + "--cannonlake", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ]) { + Ok(_) => unreachable!(), + Err(e) => { + assert!(e + .to_string() + .contains("cannot be used with one or more of the other specified arguments")); + } + } + + Ok(()) + } +} diff --git a/src/commands/config.rs b/src/commands/config.rs index 5cafac4e77e7..a434b864368d 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -17,7 +17,7 @@ pub enum ConfigCommand { impl ConfigCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { + pub fn execute(self) -> Result<()> { match self { Self::New(c) => c.execute(), } @@ -35,7 +35,7 @@ pub struct ConfigNewCommand { impl ConfigNewCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { + pub fn execute(self) -> Result<()> { let path = wasmtime_cache::create_new_config(self.path.as_ref())?; println!( diff --git a/src/commands/run.rs b/src/commands/run.rs index f8b701014689..d20b8275de99 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -28,9 +28,8 @@ use wasmtime_wasi_crypto::{ fn parse_module(s: &OsStr) -> Result { // Do not accept wasmtime subcommand names as the module name match s.to_str() { - Some("help") | Some("config") | Some("run") | Some("wasm2obj") | Some("wast") => { - Err("module name cannot be the same as a subcommand".into()) - } + Some("help") | Some("config") | Some("run") | Some("wasm2obj") | Some("wast") + | Some("compile") => Err("module name cannot be the same as a subcommand".into()), _ => Ok(s.into()), } } @@ -96,7 +95,7 @@ pub struct RunCommand { #[structopt( index = 1, required = true, - value_name = "WASM_MODULE", + value_name = "MODULE", parse(try_from_os_str = parse_module), )] module: PathBuf, @@ -127,14 +126,16 @@ pub struct RunCommand { impl RunCommand { /// Executes the command. pub fn execute(&self) -> Result<()> { - if self.common.log_to_files { - let prefix = "wasmtime.dbg."; - init_file_per_thread_logger(prefix); - } else { - pretty_env_logger::init(); + if !self.common.disable_logging { + if self.common.log_to_files { + let prefix = "wasmtime.dbg."; + init_file_per_thread_logger(prefix); + } else { + pretty_env_logger::init(); + } } - let mut config = self.common.config()?; + let mut config = self.common.config(None)?; if self.wasm_timeout.is_some() { config.interruptable(true); } diff --git a/src/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index 4688f985dcea..4a88a3051ff1 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -1,13 +1,12 @@ //! The module that implements the `wasmtime wasm2obj` command. use crate::obj::compile_to_obj; -use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions}; -use anyhow::{anyhow, Context as _, Result}; +use crate::{init_file_per_thread_logger, parse_target, pick_compilation_strategy, CommonOptions}; +use anyhow::{Context as _, Result}; use std::{ fs::File, io::Write, path::{Path, PathBuf}, - str::FromStr, }; use structopt::{clap::AppSettings, StructOpt}; use target_lexicon::Triple; @@ -16,10 +15,6 @@ use target_lexicon::Triple; pub const WASM2OBJ_AFTER_HELP: &str = "The translation is dependent on the environment chosen.\n\ The default is a dummy environment that produces placeholder values."; -fn parse_target(s: &str) -> Result { - Triple::from_str(&s).map_err(|e| anyhow!(e)) -} - /// Translates a WebAssembly module to native object file #[derive(StructOpt)] #[structopt( @@ -47,16 +42,14 @@ pub struct WasmToObjCommand { impl WasmToObjCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { - self.handle_module() - } - - fn handle_module(&self) -> Result<()> { - if self.common.log_to_files { - let prefix = "wasm2obj.dbg."; - init_file_per_thread_logger(prefix); - } else { - pretty_env_logger::init(); + pub fn execute(self) -> Result<()> { + if !self.common.disable_logging { + if self.common.log_to_files { + let prefix = "wasm2obj.dbg."; + init_file_per_thread_logger(prefix); + } else { + pretty_env_logger::init(); + } } let strategy = pick_compilation_strategy(self.common.cranelift, self.common.lightbeam)?; diff --git a/src/commands/wast.rs b/src/commands/wast.rs index 52dbff3aff3c..848e9179e653 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -25,15 +25,17 @@ pub struct WastCommand { impl WastCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { - if self.common.log_to_files { - let prefix = "wast.dbg."; - init_file_per_thread_logger(prefix); - } else { - pretty_env_logger::init(); + pub fn execute(self) -> Result<()> { + if !self.common.disable_logging { + if self.common.log_to_files { + let prefix = "wast.dbg."; + init_file_per_thread_logger(prefix); + } else { + pretty_env_logger::init(); + } } - let config = self.common.config()?; + let config = self.common.config(None)?; let store = Store::new(&Engine::new(&config)?); let mut wast_context = WastContext::new(store); diff --git a/src/lib.rs b/src/lib.rs index 39bda21f668d..7f89db761403 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ mod obj; use anyhow::{bail, Result}; use std::path::PathBuf; use structopt::StructOpt; +use target_lexicon::Triple; use wasmtime::{Config, ProfilingStrategy, Strategy}; pub use obj::compile_to_obj; @@ -91,6 +92,10 @@ struct CommonOptions { #[structopt(long, conflicts_with = "lightbeam")] cranelift: bool, + /// Disable logging. + #[structopt(long, conflicts_with = "log_to_files")] + disable_logging: bool, + /// Log to per-thread log files instead of stderr. #[structopt(long)] log_to_files: bool, @@ -107,21 +112,21 @@ struct CommonOptions { #[structopt(long)] enable_simd: bool, - /// Enable support for reference types + /// Disable support for reference types #[structopt(long)] - enable_reference_types: Option, + disable_reference_types: bool, - /// Enable support for multi-value functions + /// Disable support for multi-value functions #[structopt(long)] - enable_multi_value: Option, + disable_multi_value: bool, /// Enable support for Wasm threads #[structopt(long)] enable_threads: bool, - /// Enable support for bulk memory instructions + /// Disable support for bulk memory instructions #[structopt(long)] - enable_bulk_memory: Option, + disable_bulk_memory: bool, /// Enable support for the multi-memory proposal #[structopt(long)] @@ -151,30 +156,34 @@ struct CommonOptions { #[structopt(short = "O", long)] optimize: bool, - /// Optimization level for generated functions (0 (none), 1, 2 (most), or s - /// (size)) + /// Optimization level for generated functions: 0 (none), 1, 2 (most), or s + /// (size); defaults to "most" #[structopt( long, + value_name = "LEVEL", parse(try_from_str = parse_opt_level), - default_value = "2", )] - opt_level: wasmtime::OptLevel, + opt_level: Option, - /// Other Cranelift flags to be passed down to Cranelift. - #[structopt(long, parse(try_from_str = parse_cranelift_flag))] + /// Cranelift common flags to set. + #[structopt(long = "cranelift-flag", value_name = "NAME=VALUE", parse(try_from_str = parse_cranelift_flag))] cranelift_flags: Vec, + /// The Cranelift ISA preset to use. + #[structopt(long, value_name = "PRESET")] + cranelift_preset: Option, + /// Maximum size in bytes of wasm memory before it becomes dynamically /// relocatable instead of up-front-reserved. - #[structopt(long)] + #[structopt(long, value_name = "MAXIMUM")] static_memory_maximum_size: Option, /// Byte size of the guard region after static memories are allocated. - #[structopt(long)] + #[structopt(long, value_name = "SIZE")] static_memory_guard_size: Option, /// Byte size of the guard region after dynamic memories are allocated. - #[structopt(long)] + #[structopt(long, value_name = "SIZE")] dynamic_memory_guard_size: Option, /// Enable Cranelift's internal debug verifier (expensive) @@ -187,19 +196,22 @@ struct CommonOptions { } impl CommonOptions { - fn config(&self) -> Result { - let mut config = Config::new(); + fn config(&self, target: Option<&str>) -> Result { + let mut config = if let Some(target) = target { + Config::for_target(target)? + } else { + Config::new() + }; + config .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) .debug_info(self.debug_info) .wasm_simd(self.enable_simd || self.enable_all) - .wasm_bulk_memory(self.enable_bulk_memory.unwrap_or(true) || self.enable_all) + .wasm_bulk_memory(!self.disable_bulk_memory || self.enable_all) .wasm_reference_types( - self.enable_reference_types - .unwrap_or(cfg!(target_arch = "x86_64")) - || self.enable_all, + (!self.disable_reference_types || cfg!(target_arch = "x86_64")) || self.enable_all, ) - .wasm_multi_value(self.enable_multi_value.unwrap_or(true) || self.enable_all) + .wasm_multi_value(!self.disable_multi_value || self.enable_all) .wasm_threads(self.enable_threads || self.enable_all) .wasm_multi_memory(self.enable_multi_memory || self.enable_all) .wasm_module_linking(self.enable_module_linking || self.enable_all) @@ -207,11 +219,19 @@ impl CommonOptions { .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)? .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)? .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); + + if let Some(preset) = &self.cranelift_preset { + unsafe { + config.cranelift_flag_enable(preset)?; + } + } + for CraneliftFlag { name, value } in &self.cranelift_flags { unsafe { config.cranelift_other_flag(name, value)?; } } + if !self.disable_cache { match &self.config { Some(path) => { @@ -237,7 +257,7 @@ impl CommonOptions { fn opt_level(&self) -> wasmtime::OptLevel { match (self.optimize, self.opt_level.clone()) { (true, _) => wasmtime::OptLevel::Speed, - (false, other) => other, + (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed), } } } @@ -274,3 +294,9 @@ fn parse_cranelift_flag(name_and_value: &str) -> Result { }; Ok(CraneliftFlag { name, value }) } + +fn parse_target(s: &str) -> Result { + use std::str::FromStr; + + Triple::from_str(&s).map_err(|e| anyhow::anyhow!(e)) +} diff --git a/tests/all/module.rs b/tests/all/module.rs index 18b2f72c58c7..b8f39ecbf3d6 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -1,57 +1,61 @@ +use anyhow::Result; +use std::io::BufWriter; use wasmtime::*; #[test] fn caches_across_engines() { - let mut c = Config::new(); - c.cranelift_clear_cpu_flags(); + let c = Config::new(); let bytes = Module::new(&Engine::new(&c).unwrap(), "(module)") .unwrap() .serialize() .unwrap(); - let res = Module::deserialize( - &Engine::new(&Config::new().cranelift_clear_cpu_flags()).unwrap(), - &bytes, - ); + let res = Module::deserialize(&Engine::new(&Config::new()).unwrap(), &bytes); assert!(res.is_ok()); // differ in shared cranelift flags let res = Module::deserialize( - &Engine::new( - &Config::new() - .cranelift_clear_cpu_flags() - .cranelift_nan_canonicalization(true), - ) - .unwrap(), + &Engine::new(&Config::new().cranelift_nan_canonicalization(true)).unwrap(), &bytes, ); assert!(res.is_err()); // differ in cranelift settings let res = Module::deserialize( - &Engine::new( - &Config::new() - .cranelift_clear_cpu_flags() - .cranelift_opt_level(OptLevel::None), - ) - .unwrap(), + &Engine::new(&Config::new().cranelift_opt_level(OptLevel::None)).unwrap(), &bytes, ); assert!(res.is_err()); - // differ in cpu-specific flags + // Missing required cpu flags if cfg!(target_arch = "x86_64") { let res = Module::deserialize( - &Engine::new(unsafe { - &Config::new() - .cranelift_clear_cpu_flags() - .cranelift_other_flag("has_sse3", "true") - .unwrap() - }) - .unwrap(), + &Engine::new(&Config::new().cranelift_clear_cpu_flags()).unwrap(), &bytes, ); assert!(res.is_err()); } } + +#[test] +fn aot_compiles() -> Result<()> { + let engine = Engine::default(); + let mut writer = BufWriter::new(Vec::new()); + Module::compile( + &engine, + "(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(), + &mut writer, + )?; + + let bytes = writer.into_inner()?; + let module = Module::from_binary(&engine, &bytes)?; + + let store = Store::new(&engine); + let instance = Instance::new(&store, &module, &[])?; + + let f = instance.get_typed_func::("f")?; + assert_eq!(f.call(101).unwrap(), 101); + + Ok(()) +}