From 29d366db7bf6c8d76369a3a00d9d999acf5be106 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 24 Mar 2021 18:49:33 -0700 Subject: [PATCH 01/12] Add a compile command to Wasmtime. This commit adds a `compile` command to the Wasmtime CLI. The command can be used to Ahead-Of-Time (AOT) compile WebAssembly modules. With the `all-arch` feature enabled, AOT compilation can be performed for non-native architectures (i.e. cross-compilation). The `Module::compile` method has been added to perform AOT compilation. A few of the CLI flags relating to "on by default" Wasm features have been changed to be "--disable-XYZ" flags. A simple example of using the `wasmtime compile` command: ```text $ wasmtime compile input.wasm $ wasmtime input.cwasm ``` --- Cargo.toml | 1 + RELEASES.md | 31 +- cranelift/Cargo.toml | 1 + cranelift/codegen/meta/src/gen_settings.rs | 68 ++ cranelift/codegen/meta/src/isa/arm64/mod.rs | 3 + .../codegen/meta/src/isa/x86/settings.rs | 5 +- cranelift/codegen/src/isa/aarch64/mod.rs | 15 +- cranelift/codegen/src/isa/arm32/mod.rs | 10 +- cranelift/codegen/src/isa/mod.rs | 12 +- cranelift/codegen/src/isa/riscv/mod.rs | 19 +- cranelift/codegen/src/isa/x64/mod.rs | 13 +- cranelift/codegen/src/isa/x86/mod.rs | 19 +- cranelift/codegen/src/machinst/adapter.rs | 11 +- cranelift/codegen/src/machinst/mod.rs | 11 +- crates/environ/src/data_structures.rs | 4 +- crates/environ/src/tunables.rs | 4 +- crates/jit/Cargo.toml | 1 + crates/jit/src/code_memory.rs | 2 +- crates/jit/src/compiler.rs | 8 +- crates/jit/src/instantiate.rs | 2 +- crates/wasmtime/Cargo.toml | 3 + crates/wasmtime/src/config.rs | 51 +- crates/wasmtime/src/lib.rs | 4 + crates/wasmtime/src/module.rs | 249 +++--- crates/wasmtime/src/module/serialization.rs | 715 ++++++++++++++++++ docs/cli-options.md | 15 + src/bin/wasmtime.rs | 7 +- src/commands.rs | 3 +- src/commands/compile.rs | 413 ++++++++++ src/commands/config.rs | 4 +- src/commands/run.rs | 21 +- src/commands/wasm2obj.rs | 27 +- src/commands/wast.rs | 16 +- src/lib.rs | 72 +- tests/all/module.rs | 56 +- 35 files changed, 1618 insertions(+), 278 deletions(-) create mode 100644 crates/wasmtime/src/module/serialization.rs create mode 100644 src/commands/compile.rs diff --git a/Cargo.toml b/Cargo.toml index be37d8cec7..efdae9083e 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 dd8ca5ddd2..b19e2b2105 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 80a083250e..751f6316a2 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 a70ddccfe1..70b1872638 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 cbc21347e9..1350963242 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 dddd69abb3..2540ac2065 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 42b47b645e..9fbf7e60c5 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 5757b844d2..50e6f6f59c 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 94895b0b6e..44fbf564d3 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 500451c72e..9db8b60ffa 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 da4065f2d0..ae8b889fbd 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 272c3dfe5d..c01f8acf01 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 eb4760fae5..a75e7393f6 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 d7835a98f7..d1c96640dc 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 07f2aedaec..bbe6c1e1b9 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 4e5aba9145..8a786ae88b 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 0bf2f88e1a..b125d3a649 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 49a2d0ecd4..e22c6a46c2 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 fe94c27c02..6d2fc7caf2 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 df2cd77c31..45c57c532f 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 b36e1afb04..34481ee9e1 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 05296216f9..1fd5ee8b3e 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 066d034cbc..d843b9db43 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 b068416ee7..c9922b4f27 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 0000000000..9c469924ad --- /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 d1204314ab..5701cb48a6 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 89c1078f48..8b7c67caad 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 e9891bab99..1f9bc601f9 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 0000000000..79ea030532 --- /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 5cafac4e77..a434b86436 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 f8b7010146..d20b8275de 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 4688f985dc..4a88a3051f 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 52dbff3aff..848e9179e6 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 39bda21f66..7f89db7614 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 18b2f72c58..b8f39ecbf3 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(()) +} From 7e02940c7257011f1c38b5feb3936649c13de49f Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 29 Mar 2021 22:24:53 -0700 Subject: [PATCH 02/12] Add Wasmtime to AOT header. Co-authored-by: bjorn3 --- crates/wasmtime/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index c9922b4f27..6d1d9b3ebc 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -17,7 +17,7 @@ mod serialization; use serialization::SerializedModule; -const COMPILED_MODULE_HEADER: &[u8] = b"\0aot"; +const COMPILED_MODULE_HEADER: &[u8] = b"\0wasmtimeaot"; /// A compiled WebAssembly module, ready to be instantiated. /// From 273597903be217be42008a33dfa518520c0d4c1a Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 29 Mar 2021 22:27:43 -0700 Subject: [PATCH 03/12] Fix spelling error. --- crates/jit/src/code_memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index e22c6a46c2..9c703f6440 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -312,7 +312,7 @@ impl CodeMemory { } } - // Register all unwind entires for functions and trampolines. + // Register all unwind entries for functions and trampolines. // TODO will `u32` type for start/len be enough for large code base. for i in unwind_info { match i { From 1ce2a871499f7f9e13dce2c26ffe515935a01e09 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 30 Mar 2021 17:20:15 -0700 Subject: [PATCH 04/12] Code review feedback. * Remove `Config::for_target` in favor of setter `Config::target`. * Remove explicit setting of Cranelift flags in `Config::new` in favor of calling the `Config` methods that do the same thing. * Serialize the package version independently of the data when serializing a module. * Use struct deconstructing in module serialization to ensure tunables and features aren't missed. * Move common log initialization in the CLI into `CommonOptions`. --- crates/wasmtime/src/config.rs | 68 ++++---- crates/wasmtime/src/module.rs | 45 +++++- crates/wasmtime/src/module/serialization.rs | 162 +++++++++----------- src/commands/compile.rs | 11 +- src/commands/run.rs | 11 +- src/commands/wasm2obj.rs | 11 +- src/commands/wast.rs | 11 +- src/lib.rs | 22 ++- tests/all/module_serialize.rs | 17 ++ 9 files changed, 180 insertions(+), 178 deletions(-) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 1fd5ee8b3e..2ccfca22a0 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -392,20 +392,6 @@ 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,30 +400,15 @@ impl Config { .enable("avoid_div_traps") .expect("should be valid flag"); - // Invert cranelift's default-on verification to instead default off. - flags - .set("enable_verifier", "false") - .expect("should be valid flag"); - - // Turn on cranelift speed optimizations by default - flags - .set("opt_level", "speed") - .expect("should be valid flag"); - // We don't use probestack as a stack limit mechanism flags .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, + isa_flags: native::builder(), strategy: CompilationStrategy::Auto, #[cfg(feature = "cache")] cache_config: CacheConfig::new_cache_disabled(), @@ -446,12 +417,7 @@ impl Config { allocation_strategy: InstanceAllocationStrategy::OnDemand, max_wasm_stack: 1 << 20, wasm_backtrace_details_env_used: false, - features: WasmFeatures { - reference_types: true, - bulk_memory: true, - multi_value: true, - ..WasmFeatures::default() - }, + features: WasmFeatures::default(), max_instances: 10_000, max_tables: 10_000, max_memories: 10_000, @@ -460,10 +426,38 @@ impl Config { host_funcs: HostFuncMap::new(), async_support: false, }; + ret.cranelift_debug_verifier(false); + ret.cranelift_opt_level(OptLevel::Speed); + ret.wasm_reference_types(true); + ret.wasm_multi_value(true); + ret.wasm_bulk_memory(true); ret.wasm_backtrace_details(WasmBacktraceDetails::Environment); ret } + /// Sets the target triple for the [`Config`]. + /// + /// By default, the host target triple is used for the [`Config`]. + /// + /// This method can be used to change the target triple. + /// + /// Note that any no Cranelift flags will be inferred for the given target. + /// + /// [`Config::cranelift_clear_cpu_flags`] will reset the target triple back to + /// the host's target. + /// + /// # Errors + /// + /// This method will error if the given target triple is not supported. + pub fn target(&mut self, target: &str) -> Result<&mut Self> { + use std::str::FromStr; + self.isa_flags = native::lookup( + target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?, + )?; + + Ok(self) + } + /// Whether or not to enable support for asynchronous functions in Wasmtime. /// /// When enabled, the config can optionally define host functions with `async`. @@ -906,6 +900,8 @@ impl Config { /// Clears native CPU flags inferred from the host. /// + /// Note: this method will change the target to that of the host. + /// /// By default Wasmtime will tune generated code for the host that Wasmtime /// itself is running on. If you're compiling on one host, however, and /// shipping artifacts to another host then this behavior may not be diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 6d1d9b3ebc..bb3fcf0da6 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -17,7 +17,7 @@ mod serialization; use serialization::SerializedModule; -const COMPILED_MODULE_HEADER: &[u8] = b"\0wasmtimeaot"; +const COMPILED_MODULE_HEADER: &[u8] = b"\0wasmtime-aot"; /// A compiled WebAssembly module, ready to be instantiated. /// @@ -364,12 +364,10 @@ impl Module { // Write a header that marks this as a compiled module output.write_all(COMPILED_MODULE_HEADER)?; - bincode_options().serialize_into( - output, + Self::serialize_module( &SerializedModule::from_artifacts(engine.compiler(), &artifacts, &types), - )?; - - Ok(()) + output, + ) } /// Returns the type signature of this module. @@ -392,10 +390,24 @@ impl Module { /// Serialize compilation artifacts to the buffer. See also `deserialize`. pub fn serialize(&self) -> Result> { let mut buffer = Vec::new(); - bincode_options().serialize_into(&mut buffer, &SerializedModule::new(self))?; + Self::serialize_module(&SerializedModule::new(self), &mut buffer)?; Ok(buffer) } + fn serialize_module(module: &SerializedModule, mut output: impl Write) -> Result<()> { + // Preface the data with a version so we can do a version check independent + // of the serialized data. + let version = env!("CARGO_PKG_VERSION"); + assert!( + version.len() < 256, + "package version must be less than 256 bytes" + ); + output.write(&[version.len() as u8])?; + output.write_all(version.as_bytes())?; + bincode_options().serialize_into(output, module)?; + Ok(()) + } + /// 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 @@ -406,8 +418,25 @@ impl Module { /// for modifications or corruptions. All responsibly of signing and its /// verification falls on the embedder. pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { + if serialized.is_empty() { + bail!("serialized data data is empty"); + } + + let version_len = serialized[0] as usize; + if serialized.len() < version_len + 1 { + bail!("serialized data is malformed"); + } + + let version = std::str::from_utf8(&serialized[1..1 + version_len])?; + if version != env!("CARGO_PKG_VERSION") { + bail!( + "Module was compiled with incompatible Wasmtime version '{}'", + version + ); + } + bincode_options() - .deserialize::>(serialized) + .deserialize::>(&serialized[1 + version_len..]) .context("Deserialize compilation artifacts")? .into_module(engine) } diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 9c469924ad..3daa7b9259 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -32,18 +32,32 @@ struct WasmFeatures { impl From<&wasmparser::WasmFeatures> for WasmFeatures { fn from(other: &wasmparser::WasmFeatures) -> Self { + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = other; + 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, + reference_types: *reference_types, + multi_value: *multi_value, + bulk_memory: *bulk_memory, + module_linking: *module_linking, + simd: *simd, + threads: *threads, + tail_call: *tail_call, + deterministic_only: *deterministic_only, + multi_memory: *multi_memory, + exceptions: *exceptions, + memory64: *memory64, } } } @@ -149,7 +163,6 @@ impl<'a> SerializedModuleData<'a> { #[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 @@ -192,7 +205,6 @@ impl<'a> SerializedModule<'a> { 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()), @@ -209,7 +221,6 @@ impl<'a> SerializedModule<'a> { let compiler = engine.compiler(); let isa = compiler.isa(); - self.check_version()?; self.check_triple(isa)?; self.check_isa_flags(isa)?; self.check_strategy(compiler)?; @@ -262,17 +273,6 @@ impl<'a> SerializedModule<'a> { } } - 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))?; @@ -368,120 +368,115 @@ impl<'a> SerializedModule<'a> { } fn check_tunables(&self, compiler: &Compiler) -> Result<()> { + let Tunables { + static_memory_bound, + static_memory_offset_guard_size, + dynamic_memory_offset_guard_size, + generate_native_debuginfo, + parse_wasm_debuginfo, + interruptable, + consume_fuel, + static_memory_bound_is_maximum, + } = self.tunables; + let other = compiler.tunables(); Self::check_int( - self.tunables.static_memory_bound, + static_memory_bound, other.static_memory_bound, "static memory bound", )?; Self::check_int( - self.tunables.static_memory_offset_guard_size, + 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, + dynamic_memory_offset_guard_size, other.dynamic_memory_offset_guard_size, "dynamic memory guard size", )?; Self::check_bool( - self.tunables.generate_native_debuginfo, + generate_native_debuginfo, other.generate_native_debuginfo, "debug information support", )?; Self::check_bool( - self.tunables.parse_wasm_debuginfo, + parse_wasm_debuginfo, other.parse_wasm_debuginfo, "WebAssembly backtrace support", )?; + Self::check_bool(interruptable, other.interruptable, "interruption support")?; + Self::check_bool(consume_fuel, other.consume_fuel, "fuel 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, + 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 WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = self.features; + let other = compiler.features(); Self::check_bool( - self.features.reference_types, + reference_types, other.reference_types, "WebAssembly reference types support", )?; Self::check_bool( - self.features.multi_value, + multi_value, other.multi_value, "WebAssembly multi-value support", )?; Self::check_bool( - self.features.bulk_memory, + bulk_memory, other.bulk_memory, "WebAssembly bulk memory support", )?; Self::check_bool( - self.features.module_linking, + module_linking, other.module_linking, "WebAssembly module linking support", )?; - Self::check_bool(self.features.simd, other.simd, "WebAssembly SIMD support")?; + Self::check_bool(simd, other.simd, "WebAssembly SIMD support")?; + Self::check_bool(threads, other.threads, "WebAssembly threads support")?; + Self::check_bool(tail_call, other.tail_call, "WebAssembly tail-call 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, + deterministic_only, other.deterministic_only, "WebAssembly deterministic-only support", )?; Self::check_bool( - self.features.multi_memory, + multi_memory, other.multi_memory, "WebAssembly multi-memory support", )?; Self::check_bool( - self.features.exceptions, + exceptions, other.exceptions, "WebAssembly exceptions support", )?; Self::check_bool( - self.features.memory64, + 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(()) } } @@ -491,25 +486,6 @@ 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(); diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 79ea030532..77a38940c6 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -1,6 +1,6 @@ //! The module that implements the `wasmtime wast` command. -use crate::{init_file_per_thread_logger, CommonOptions}; +use crate::CommonOptions; use anyhow::{anyhow, bail, Context, Result}; use std::fs::{self, File}; use std::io::BufWriter; @@ -148,14 +148,7 @@ pub struct CompileCommand { 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(); - } - } + self.common.init_logging(); let target = self .target diff --git a/src/commands/run.rs b/src/commands/run.rs index d20b8275de..83ede6a06c 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,6 +1,6 @@ //! The module that implements the `wasmtime run` command. -use crate::{init_file_per_thread_logger, CommonOptions}; +use crate::CommonOptions; use anyhow::{bail, Context as _, Result}; use std::thread; use std::time::Duration; @@ -126,14 +126,7 @@ pub struct RunCommand { impl RunCommand { /// Executes the command. pub fn execute(&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(); - } - } + self.common.init_logging(); let mut config = self.common.config(None)?; if self.wasm_timeout.is_some() { diff --git a/src/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index 4a88a3051f..efb118d615 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -1,7 +1,7 @@ //! The module that implements the `wasmtime wasm2obj` command. use crate::obj::compile_to_obj; -use crate::{init_file_per_thread_logger, parse_target, pick_compilation_strategy, CommonOptions}; +use crate::{parse_target, pick_compilation_strategy, CommonOptions}; use anyhow::{Context as _, Result}; use std::{ fs::File, @@ -43,14 +43,7 @@ pub struct WasmToObjCommand { impl WasmToObjCommand { /// Executes the command. 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(); - } - } + self.common.init_logging(); let strategy = pick_compilation_strategy(self.common.cranelift, self.common.lightbeam)?; diff --git a/src/commands/wast.rs b/src/commands/wast.rs index 848e9179e6..b757db41b1 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -1,6 +1,6 @@ //! The module that implements the `wasmtime wast` command. -use crate::{init_file_per_thread_logger, CommonOptions}; +use crate::CommonOptions; use anyhow::{Context as _, Result}; use std::path::PathBuf; use structopt::{clap::AppSettings, StructOpt}; @@ -26,14 +26,7 @@ pub struct WastCommand { impl WastCommand { /// Executes the command. 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(); - } - } + self.common.init_logging(); let config = self.common.config(None)?; let store = Store::new(&Engine::new(&config)?); diff --git a/src/lib.rs b/src/lib.rs index 7f89db7614..259be6de64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,12 +196,24 @@ struct CommonOptions { } impl CommonOptions { - fn config(&self, target: Option<&str>) -> Result { - let mut config = if let Some(target) = target { - Config::for_target(target)? + fn init_logging(&self) { + if self.disable_logging { + return; + } + if self.log_to_files { + let prefix = "wasmtime.dbg."; + init_file_per_thread_logger(prefix); } else { - Config::new() - }; + pretty_env_logger::init(); + } + } + fn config(&self, target: Option<&str>) -> Result { + let mut config = Config::new(); + + // Set the target before setting any cranelift options + if let Some(target) = target { + config.target(target)?; + } config .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs index a77f62b077..dab73f6775 100644 --- a/tests/all/module_serialize.rs +++ b/tests/all/module_serialize.rs @@ -11,6 +11,23 @@ fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result Ok(Instance::new(&store, &module, &[])?) } +#[test] +fn test_version_mismatch() -> Result<()> { + let engine = Engine::default(); + let mut buffer = serialize(&engine, "(module)")?; + buffer[1] = 'x' as u8; + + match Module::deserialize(&engine, &buffer) { + Ok(_) => bail!("expected deserialization to fail"), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with incompatible Wasmtime version 'x.25.0'" + ), + } + + Ok(()) +} + #[test] fn test_module_serialize_simple() -> Result<()> { let buffer = serialize( From 0000aa064662a7ae9b87d350886db27cf5089adc Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 30 Mar 2021 23:56:56 -0700 Subject: [PATCH 05/12] Replace WebAssembly feature CLI options with `--wasm-features`. This commit hides the existing WebAssembly feature CLI options (e.g. `--enable-simd`) and adds a `--wasm-features` flag that enables multiple (or all) WebAssembly features. Features can be disabled by prefixing the value with `-`, e.g. `--wasm-features=-simd`. --- Cargo.lock | 1 + Cargo.toml | 1 + RELEASES.md | 10 +- src/bin/wasmtime.rs | 4 +- src/commands/compile.rs | 43 ++++-- src/commands/run.rs | 8 +- src/commands/wasm2obj.rs | 16 +- src/commands/wast.rs | 7 + src/lib.rs | 324 +++++++++++++++++++++++++++++++++++---- 9 files changed, 356 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33240d0d00..2626813a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3273,6 +3273,7 @@ dependencies = [ "file-per-thread-logger", "filecheck", "humantime 2.1.0", + "lazy_static", "libc", "log", "more-asserts", diff --git a/Cargo.toml b/Cargo.toml index efdae9083e..a66027c480 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ log = "0.4.8" rayon = "1.2.1" humantime = "2.0.0" wasmparser = "0.77.0" +lazy_static = "1.4.0" [dev-dependencies] env_logger = "0.8.1" diff --git a/RELEASES.md b/RELEASES.md index b19e2b2105..07a048bbe8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,16 +16,20 @@ ### Changed +* Wasmtime CLI options to enable WebAssembly features have been replaced with a + singular `--wasm-features` option. The previous options are still supported, but + are not displayed in help text. + * 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. + `--wasm-features=-reference-types`. * Breaking: the CLI option `--enable-multi-value=false` has been changed to - `--disable-multi-value` as it is enabled by default. + `--wasm-features=-multi-value`. * Breaking: the CLI option `--enable-bulk-memory=false` has been changed to - `--disable-bulk-memory` as it is enabled by default. + `--wasm-features=-bulk-memory`. * Modules serialized with `Module::serialize` can now be deserialized with `Module::deserialize` on a compatible host that does not have to match the diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 8b7c67caad..46490227ef 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::{ - CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP, + CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, }; /// Wasmtime WebAssembly Runtime @@ -43,7 +43,7 @@ enum WasmtimeApp { /// Runs a WebAssembly module Run(RunCommand), /// Translates a WebAssembly module to native object file - #[structopt(name = "wasm2obj", after_help = WASM2OBJ_AFTER_HELP)] + #[structopt(name = "wasm2obj")] WasmToObj(WasmToObjCommand), /// Runs a WebAssembly test script file Wast(WastCommand), diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 77a38940c6..b6656e1eca 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -12,6 +12,31 @@ use structopt::{ use target_lexicon::Triple; use wasmtime::{Config, Engine, Module}; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + format!( + "By default, no CPU features or presets will be enabled for the compilation.\n\ + \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", + crate::WASM_FEATURES.as_str() + ) + }; +} + /// Compiles a WebAssembly module. #[derive(StructOpt)] #[structopt( @@ -22,23 +47,7 @@ use wasmtime::{Config, Engine, Module}; 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" + after_help = AFTER_HELP.as_str() )] pub struct CompileCommand { #[structopt(flatten)] diff --git a/src/commands/run.rs b/src/commands/run.rs index 83ede6a06c..6f27dc9957 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -68,9 +68,15 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> { Ok((parts[0].into(), parts[1].into())) } +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + crate::WASM_FEATURES.to_string() + }; +} + /// Runs a WebAssembly module #[derive(StructOpt)] -#[structopt(name = "run", setting = AppSettings::TrailingVarArg)] +#[structopt(name = "run", setting = AppSettings::TrailingVarArg, after_help = AFTER_HELP.as_str())] pub struct RunCommand { #[structopt(flatten)] common: CommonOptions, diff --git a/src/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index efb118d615..ab914d8edc 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -11,9 +11,17 @@ use std::{ use structopt::{clap::AppSettings, StructOpt}; use target_lexicon::Triple; -/// The after help text for the `wasm2obj` command. -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."; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + format!( + "The translation is dependent on the environment chosen.\n\ + The default is a dummy environment that produces placeholder values.\n\ + \n\ + {}", + crate::WASM_FEATURES.as_str() + ) + }; +} /// Translates a WebAssembly module to native object file #[derive(StructOpt)] @@ -21,7 +29,7 @@ pub const WASM2OBJ_AFTER_HELP: &str = "The translation is dependent on the envir name = "wasm2obj", version = env!("CARGO_PKG_VERSION"), setting = AppSettings::ColoredHelp, - after_help = WASM2OBJ_AFTER_HELP, + after_help = AFTER_HELP.as_str(), )] pub struct WasmToObjCommand { #[structopt(flatten)] diff --git a/src/commands/wast.rs b/src/commands/wast.rs index b757db41b1..cd749e61a6 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -7,12 +7,19 @@ use structopt::{clap::AppSettings, StructOpt}; use wasmtime::{Engine, Store}; use wasmtime_wast::WastContext; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + crate::WASM_FEATURES.to_string() + }; +} + /// Runs a WebAssembly test script file #[derive(StructOpt)] #[structopt( name = "wast", version = env!("CARGO_PKG_VERSION"), setting = AppSettings::ColoredHelp, + after_help = AFTER_HELP.as_str(), )] pub struct WastCommand { #[structopt(flatten)] diff --git a/src/lib.rs b/src/lib.rs index 259be6de64..39af826d03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,52 @@ ) )] +const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ + ("all", "enables all supported WebAssembly features"), + ( + "bulk-memory", + "enables support for bulk memory instructions", + ), + ( + "module-linking", + "enables support for the module-linking proposal", + ), + ( + "multi-memory", + "enables support for the multi-memory proposal", + ), + ("multi-value", "enables support for multi-value functions"), + ("reference-types", "enables support for reference types"), + ("simd", "enables support for proposed SIMD instructions"), + ("threads", "enables support for WebAssembly threads"), +]; + +lazy_static::lazy_static! { + static ref WASM_FEATURES: String = { + use std::fmt::Write; + + let mut s = String::new(); + writeln!(&mut s, "Supported values for `--wasm-features`:").unwrap(); + writeln!(&mut s).unwrap(); + + let max = SUPPORTED_WASM_FEATURES.iter().max_by_key(|(name, _)| name.len()).unwrap(); + + for (name, desc) in SUPPORTED_WASM_FEATURES.iter() { + writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap(); + } + + writeln!(&mut s).unwrap(); + writeln!(&mut s, "Features prefixed with '-' will be disabled.").unwrap(); + + s + }; +} + pub mod commands; mod obj; use anyhow::{bail, Result}; +use std::collections::HashMap; use std::path::PathBuf; use structopt::StructOpt; use target_lexicon::Triple; @@ -108,38 +150,42 @@ struct CommonOptions { #[structopt(long)] disable_cache: bool, - /// Enable support for proposed SIMD instructions - #[structopt(long)] + /// Enable support for proposed SIMD instructions (deprecated; use `--wasm-features=simd`) + #[structopt(long, hidden = true)] enable_simd: bool, - /// Disable support for reference types - #[structopt(long)] - disable_reference_types: bool, + /// Enable support for reference types (deprecated; use `--wasm-features=reference-types`) + #[structopt(long, hidden = true)] + enable_reference_types: bool, - /// Disable support for multi-value functions - #[structopt(long)] - disable_multi_value: bool, + /// Enable support for multi-value functions (deprecated; use `--wasm-features=multi-value`) + #[structopt(long, hidden = true)] + enable_multi_value: bool, - /// Enable support for Wasm threads - #[structopt(long)] + /// Enable support for Wasm threads (deprecated; use `--wasm-features=threads`) + #[structopt(long, hidden = true)] enable_threads: bool, - /// Disable support for bulk memory instructions - #[structopt(long)] - disable_bulk_memory: bool, + /// Enable support for bulk memory instructions (deprecated; use `--wasm-features=bulk-memory`) + #[structopt(long, hidden = true)] + enable_bulk_memory: bool, - /// Enable support for the multi-memory proposal - #[structopt(long)] + /// Enable support for the multi-memory proposal (deprecated; use `--wasm-features=multi-memory`) + #[structopt(long, hidden = true)] enable_multi_memory: bool, - /// Enable support for the module-linking proposal - #[structopt(long)] + /// Enable support for the module-linking proposal (deprecated; use `--wasm-features=module-linking`) + #[structopt(long, hidden = true)] enable_module_linking: bool, - /// Enable all experimental Wasm features - #[structopt(long)] + /// Enable all experimental Wasm features (deprecated; use `--wasm-features=all`) + #[structopt(long, hidden = true)] enable_all: bool, + /// Enables or disables WebAssembly features + #[structopt(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] + wasm_features: Option, + /// Use Lightbeam for all compilation #[structopt(long, conflicts_with = "cranelift")] lightbeam: bool, @@ -156,12 +202,13 @@ struct CommonOptions { #[structopt(short = "O", long)] optimize: bool, - /// Optimization level for generated functions: 0 (none), 1, 2 (most), or s - /// (size); defaults to "most" + /// Optimization level for generated functions + /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most" #[structopt( long, value_name = "LEVEL", parse(try_from_str = parse_opt_level), + verbatim_doc_comment, )] opt_level: Option, @@ -207,6 +254,7 @@ impl CommonOptions { pretty_env_logger::init(); } } + fn config(&self, target: Option<&str>) -> Result { let mut config = Config::new(); @@ -218,20 +266,13 @@ impl CommonOptions { 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.disable_bulk_memory || self.enable_all) - .wasm_reference_types( - (!self.disable_reference_types || cfg!(target_arch = "x86_64")) || 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) .cranelift_opt_level(self.opt_level()) .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)? .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)? .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); + self.enable_wasm_features(&mut config); + if let Some(preset) = &self.cranelift_preset { unsafe { config.cranelift_flag_enable(preset)?; @@ -254,18 +295,39 @@ impl CommonOptions { } } } + if let Some(max) = self.static_memory_maximum_size { config.static_memory_maximum_size(max); } + if let Some(size) = self.static_memory_guard_size { config.static_memory_guard_size(size); } + if let Some(size) = self.dynamic_memory_guard_size { config.dynamic_memory_guard_size(size); } + Ok(config) } + fn enable_wasm_features(&self, config: &mut Config) { + let features = self.wasm_features.unwrap_or_default(); + + config + .wasm_simd(features.simd || self.enable_simd || self.enable_all) + .wasm_bulk_memory(features.bulk_memory || self.enable_bulk_memory || self.enable_all) + .wasm_reference_types( + features.reference_types || self.enable_reference_types || self.enable_all, + ) + .wasm_multi_value(features.multi_value || self.enable_multi_value || self.enable_all) + .wasm_threads(features.threads || self.enable_threads || self.enable_all) + .wasm_multi_memory(features.multi_memory || self.enable_multi_memory || self.enable_all) + .wasm_module_linking( + features.module_linking || self.enable_module_linking || self.enable_all, + ); + } + fn opt_level(&self) -> wasmtime::OptLevel { match (self.optimize, self.opt_level.clone()) { (true, _) => wasmtime::OptLevel::Speed, @@ -287,6 +349,59 @@ fn parse_opt_level(opt_level: &str) -> Result { } } +fn parse_wasm_features(features: &str) -> Result { + let features = features.trim(); + + let mut all = None; + let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES + .iter() + .map(|(name, _)| (name.to_string(), None)) + .collect(); + + if features == "all" { + all = Some(true); + } else if features == "-all" { + all = Some(false); + } else { + for feature in features.split(',') { + let feature = feature.trim(); + + if feature.is_empty() { + continue; + } + + let (feature, value) = if feature.starts_with('-') { + (&feature[1..], false) + } else { + (feature, true) + }; + + if feature == "all" { + bail!("'all' cannot be specified with other WebAssembly features"); + } + + match values.get_mut(feature) { + Some(v) => *v = Some(value), + None => bail!("unsupported WebAssembly feature '{}'", feature), + } + } + } + + Ok(wasmparser::WasmFeatures { + reference_types: all.unwrap_or(values["reference-types"].unwrap_or(true)), + multi_value: all.unwrap_or(values["multi-value"].unwrap_or(true)), + bulk_memory: all.unwrap_or(values["bulk-memory"].unwrap_or(true)), + module_linking: all.unwrap_or(values["module-linking"].unwrap_or(false)), + simd: all.unwrap_or(values["simd"].unwrap_or(false)), + threads: all.unwrap_or(values["threads"].unwrap_or(false)), + tail_call: false, + deterministic_only: false, + multi_memory: all.unwrap_or(values["multi-memory"].unwrap_or(false)), + exceptions: false, + memory64: false, + }) +} + struct CraneliftFlag { name: String, value: String, @@ -309,6 +424,153 @@ fn parse_cranelift_flag(name_and_value: &str) -> Result { fn parse_target(s: &str) -> Result { use std::str::FromStr; - Triple::from_str(&s).map_err(|e| anyhow::anyhow!(e)) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_all_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=all"])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(reference_types); + assert!(multi_value); + assert!(bulk_memory); + assert!(module_linking); + assert!(simd); + assert!(threads); + assert!(!tail_call); // Not supported + assert!(!deterministic_only); // Not supported + assert!(multi_memory); + assert!(!exceptions); // Not supported + assert!(!memory64); // Not supported + + Ok(()) + } + + #[test] + fn test_no_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=-all"])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(!reference_types); + assert!(!multi_value); + assert!(!bulk_memory); + assert!(!module_linking); + assert!(!simd); + assert!(!threads); + assert!(!tail_call); + assert!(!deterministic_only); + assert!(!multi_memory); + assert!(!exceptions); + assert!(!memory64); + + Ok(()) + } + + #[test] + fn test_multiple_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec![ + "foo", + "--wasm-features=-reference-types,simd,multi-memory", + ])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(!reference_types); + assert!(multi_value); + assert!(bulk_memory); + assert!(!module_linking); + assert!(simd); + assert!(!threads); + assert!(!tail_call); // Not supported + assert!(!deterministic_only); // Not supported + assert!(multi_memory); + assert!(!exceptions); // Not supported + assert!(!memory64); // Not supported + + Ok(()) + } + + macro_rules! feature_test { + ($test_name:ident, $name:ident, $flag:literal) => { + #[test] + fn $test_name() -> Result<()> { + let options = + CommonOptions::from_iter_safe(vec!["foo", concat!("--wasm-features=", $flag)])?; + + let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert!($name); + + let options = CommonOptions::from_iter_safe(vec![ + "foo", + concat!("--wasm-features=-", $flag), + ])?; + + let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert!(!$name); + + Ok(()) + } + }; + } + + feature_test!( + test_reference_types_feature, + reference_types, + "reference-types" + ); + feature_test!(test_multi_value_feature, multi_value, "multi-value"); + feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory"); + feature_test!( + test_module_linking_feature, + module_linking, + "module-linking" + ); + feature_test!(test_simd_feature, simd, "simd"); + feature_test!(test_threads_feature, threads, "threads"); + feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); +} From a474524d3b94d41f0f656d1a3da0c8cfae0f809b Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 31 Mar 2021 11:45:07 -0700 Subject: [PATCH 06/12] Code review feedback. * Removed `Config::cranelift_clear_cpu_flags`. * Renamed `Config::cranelift_other_flag` to `Config::cranelift::flag_set`. * Renamed `--cranelift-flag` to `--cranelift-set`. * Renamed `--cranelift-preset` to `--cranelift-enable`. --- RELEASES.md | 17 ++++++++++++++--- crates/wasmtime/src/config.rs | 24 +++--------------------- src/lib.rs | 28 +++++++++++----------------- tests/all/module.rs | 11 ++++++++--- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 07a048bbe8..dc19448778 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,8 +11,14 @@ * 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. +* Added the `Config::target` method to change the compilation target of the + configuration. This can be used in conjunction with `Module::compile` to target + a different host triple than the current one. + +* Added the `Config::cranelift_flag_enable` method to enable setting Cranelift + boolean flags or presets in a config. + +* Added CLI option `--cranelift-enable` to enable boolean settings and ISA presets. ### Changed @@ -20,7 +26,12 @@ singular `--wasm-features` option. The previous options are still supported, but are not displayed in help text. -* Breaking: the CLI option `--cranelift-flags` was changed to `--cranelift-flag`. +* Breaking: `Config::cranelift_clear_cpu_flags` was removed. Use `Config::target` + to clear the CPU flags for the host's target. + +* Breaking: `Config::cranelift_other_flag` was renamed to `Config::cranelift_flag_set`. + +* Breaking: the CLI option `--cranelift-flags` was changed to `--cranelift-set`. * Breaking: the CLI option `--enable-reference-types=false` has been changed to `--wasm-features=-reference-types`. diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 2ccfca22a0..43bd809c84 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -441,10 +441,8 @@ impl Config { /// /// This method can be used to change the target triple. /// - /// Note that any no Cranelift flags will be inferred for the given target. - /// - /// [`Config::cranelift_clear_cpu_flags`] will reset the target triple back to - /// the host's target. + /// Cranelift flags will not be inferred for the given target and any + /// existing target-specific Cranelift flags will be cleared. /// /// # Errors /// @@ -898,22 +896,6 @@ impl Config { self } - /// Clears native CPU flags inferred from the host. - /// - /// Note: this method will change the target to that of the host. - /// - /// By default Wasmtime will tune generated code for the host that Wasmtime - /// itself is running on. If you're compiling on one host, however, and - /// shipping artifacts to another host then this behavior may not be - /// desired. This function will clear all inferred native CPU features. - /// - /// To enable CPU features afterwards it's recommended to use the - /// [`Config::cranelift_other_flag`] method. - pub fn cranelift_clear_cpu_flags(&mut self) -> &mut Self { - self.isa_flags = native::builder_without_flags(); - self - } - /// Allows setting a Cranelift boolean flag or preset. This allows /// fine-tuning of Cranelift settings. /// @@ -954,7 +936,7 @@ impl Config { /// /// This method can fail if the flag's name does not exist, or the value is not appropriate for /// the flag type. - pub unsafe fn cranelift_other_flag(&mut self, name: &str, value: &str) -> Result<&mut Self> { + pub unsafe fn cranelift_flag_set(&mut self, name: &str, value: &str) -> Result<&mut Self> { if let Err(err) = self.flags.set(name, value) { match err { SetError::BadName(_) => { diff --git a/src/lib.rs b/src/lib.rs index 39af826d03..a89a7e7835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,12 +213,12 @@ struct CommonOptions { opt_level: Option, /// Cranelift common flags to set. - #[structopt(long = "cranelift-flag", value_name = "NAME=VALUE", parse(try_from_str = parse_cranelift_flag))] - cranelift_flags: Vec, + #[structopt(long = "cranelift-set", value_name = "NAME=VALUE", parse(try_from_str = parse_cranelift_flag))] + cranelift_set: Vec<(String, String)>, - /// The Cranelift ISA preset to use. - #[structopt(long, value_name = "PRESET")] - cranelift_preset: Option, + /// The Cranelift boolean setting or preset to enable. + #[structopt(long, value_name = "SETTING")] + cranelift_enable: Vec, /// Maximum size in bytes of wasm memory before it becomes dynamically /// relocatable instead of up-front-reserved. @@ -273,15 +273,15 @@ impl CommonOptions { self.enable_wasm_features(&mut config); - if let Some(preset) = &self.cranelift_preset { + for name in &self.cranelift_enable { unsafe { - config.cranelift_flag_enable(preset)?; + config.cranelift_flag_enable(name)?; } } - for CraneliftFlag { name, value } in &self.cranelift_flags { + for (name, value) in &self.cranelift_set { unsafe { - config.cranelift_other_flag(name, value)?; + config.cranelift_flag_set(name, value)?; } } @@ -401,13 +401,7 @@ fn parse_wasm_features(features: &str) -> Result { memory64: false, }) } - -struct CraneliftFlag { - name: String, - value: String, -} - -fn parse_cranelift_flag(name_and_value: &str) -> Result { +fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> { let mut split = name_and_value.splitn(2, '='); let name = if let Some(name) = split.next() { name.to_string() @@ -419,7 +413,7 @@ fn parse_cranelift_flag(name_and_value: &str) -> Result { } else { bail!("missing value in cranelift flag"); }; - Ok(CraneliftFlag { name, value }) + Ok((name, value)) } fn parse_target(s: &str) -> Result { diff --git a/tests/all/module.rs b/tests/all/module.rs index b8f39ecbf3..0f811ac085 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -16,14 +16,14 @@ fn caches_across_engines() { // differ in shared cranelift flags let res = Module::deserialize( - &Engine::new(&Config::new().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_opt_level(OptLevel::None)).unwrap(), + &Engine::new(Config::new().cranelift_opt_level(OptLevel::None)).unwrap(), &bytes, ); assert!(res.is_err()); @@ -31,7 +31,12 @@ fn caches_across_engines() { // Missing required cpu flags if cfg!(target_arch = "x86_64") { let res = Module::deserialize( - &Engine::new(&Config::new().cranelift_clear_cpu_flags()).unwrap(), + &Engine::new( + Config::new() + .target(&target_lexicon::Triple::host().to_string()) + .unwrap(), + ) + .unwrap(), &bytes, ); assert!(res.is_err()); From abf3bf29f997aa93734277c987d29e5227548b61 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 31 Mar 2021 22:44:13 -0700 Subject: [PATCH 07/12] Add a `wasmtime settings` command to print Cranelift settings. This commit adds the `wasmtime settings` command to print out available Cranelift settings for a target (defaults to the host). The compile command has been updated to remove the Cranelift ISA options in favor of encouraging users to use `wasmtime settings` to discover what settings are available. This will reduce the maintenance cost for syncing the compile command with Cranelift ISA flags. --- cranelift/codegen/meta/src/cdsl/settings.rs | 51 +++- cranelift/codegen/meta/src/gen_settings.rs | 4 +- cranelift/codegen/meta/src/isa/arm64/mod.rs | 5 +- cranelift/codegen/meta/src/isa/riscv/mod.rs | 6 + .../codegen/meta/src/isa/x86/settings.rs | 87 +++++-- cranelift/codegen/meta/src/shared/settings.rs | 183 +++++++------- cranelift/codegen/src/isa/mod.rs | 10 + cranelift/codegen/src/settings.rs | 55 ++++ crates/environ/src/data_structures.rs | 2 +- crates/wasmtime/src/module.rs | 9 + src/bin/wasmtime.rs | 5 +- src/commands.rs | 3 +- src/commands/compile.rs | 235 ++++-------------- src/commands/settings.rs | 103 ++++++++ src/lib.rs | 15 +- tests/all/module.rs | 17 ++ 16 files changed, 478 insertions(+), 312 deletions(-) create mode 100644 src/commands/settings.rs diff --git a/cranelift/codegen/meta/src/cdsl/settings.rs b/cranelift/codegen/meta/src/cdsl/settings.rs index 217bad9955..52c51d54a8 100644 --- a/cranelift/codegen/meta/src/cdsl/settings.rs +++ b/cranelift/codegen/meta/src/cdsl/settings.rs @@ -20,6 +20,7 @@ pub(crate) enum SpecificSetting { #[derive(Hash, PartialEq, Eq)] pub(crate) struct Setting { pub name: &'static str, + pub description: &'static str, pub comment: &'static str, pub specific: SpecificSetting, pub byte_offset: u8, @@ -88,6 +89,7 @@ impl Into for PresetIndex { #[derive(Hash, PartialEq, Eq)] pub(crate) struct Preset { pub name: &'static str, + pub description: &'static str, values: Vec, } @@ -169,6 +171,7 @@ pub(crate) enum ProtoSpecificSetting { /// This is the information provided during building for a setting. struct ProtoSetting { name: &'static str, + description: &'static str, comment: &'static str, specific: ProtoSpecificSetting, } @@ -251,11 +254,13 @@ impl SettingGroupBuilder { fn add_setting( &mut self, name: &'static str, + description: &'static str, comment: &'static str, specific: ProtoSpecificSetting, ) { self.settings.push(ProtoSetting { name, + description, comment, specific, }) @@ -264,6 +269,7 @@ impl SettingGroupBuilder { pub fn add_bool( &mut self, name: &'static str, + description: &'static str, comment: &'static str, default: bool, ) -> BoolSettingIndex { @@ -271,28 +277,55 @@ impl SettingGroupBuilder { self.predicates.is_empty(), "predicates must be added after the boolean settings" ); - self.add_setting(name, comment, ProtoSpecificSetting::Bool(default)); + self.add_setting( + name, + description, + comment, + ProtoSpecificSetting::Bool(default), + ); BoolSettingIndex(self.settings.len() - 1) } pub fn add_enum( &mut self, name: &'static str, + description: &'static str, comment: &'static str, values: Vec<&'static str>, ) { - self.add_setting(name, comment, ProtoSpecificSetting::Enum(values)); + self.add_setting( + name, + description, + comment, + ProtoSpecificSetting::Enum(values), + ); } - pub fn add_num(&mut self, name: &'static str, comment: &'static str, default: u8) { - self.add_setting(name, comment, ProtoSpecificSetting::Num(default)); + pub fn add_num( + &mut self, + name: &'static str, + description: &'static str, + comment: &'static str, + default: u8, + ) { + self.add_setting( + name, + description, + comment, + ProtoSpecificSetting::Num(default), + ); } pub fn add_predicate(&mut self, name: &'static str, node: PredicateNode) { self.predicates.push(ProtoPredicate { name, node }); } - pub fn add_preset(&mut self, name: &'static str, args: Vec) -> PresetIndex { + pub fn add_preset( + &mut self, + name: &'static str, + description: &'static str, + args: Vec, + ) -> PresetIndex { let mut values = Vec::new(); for arg in args { match arg { @@ -302,7 +335,11 @@ impl SettingGroupBuilder { PresetType::BoolSetting(index) => values.push(index), } } - self.presets.push(Preset { name, values }); + self.presets.push(Preset { + name, + description, + values, + }); PresetIndex(self.presets.len() - 1) } @@ -347,6 +384,7 @@ impl SettingGroupBuilder { group.settings.push(Setting { name: s.name, + description: s.description, comment: s.comment, byte_offset, specific, @@ -367,6 +405,7 @@ impl SettingGroupBuilder { }; group.settings.push(Setting { name: s.name, + description: s.description, comment: s.comment, byte_offset: byte_offset + predicate_number / 8, specific: SpecificSetting::Bool(BoolSetting { diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index 70b1872638..2808c2a777 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -202,7 +202,7 @@ fn gen_enum_types(group: &SettingGroup, fmt: &mut Formatter) { /// Emit a getter function for `setting`. fn gen_getter(setting: &Setting, fmt: &mut Formatter) { - fmt.doc_comment(setting.comment); + fmt.doc_comment(format!("{}\n{}", setting.description, setting.comment)); match setting.specific { SpecificSetting::Bool(BoolSetting { predicate_number, .. @@ -320,6 +320,7 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) { fmtln!(fmt, "detail::Descriptor {"); fmt.indent(|fmt| { fmtln!(fmt, "name: \"{}\",", setting.name); + fmtln!(fmt, "description: \"{}\",", setting.description); fmtln!(fmt, "offset: {},", setting.byte_offset); match setting.specific { SpecificSetting::Bool(BoolSetting { bit_offset, .. }) => { @@ -352,6 +353,7 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) { fmtln!(fmt, "detail::Descriptor {"); fmt.indent(|fmt| { fmtln!(fmt, "name: \"{}\",", preset.name); + fmtln!(fmt, "description: \"{}\",", preset.description); fmtln!(fmt, "offset: {},", (idx as u8) * group.settings_size); fmtln!(fmt, "detail: detail::Detail::Preset,"); }); diff --git a/cranelift/codegen/meta/src/isa/arm64/mod.rs b/cranelift/codegen/meta/src/isa/arm64/mod.rs index 1350963242..4277e147a7 100644 --- a/cranelift/codegen/meta/src/isa/arm64/mod.rs +++ b/cranelift/codegen/meta/src/isa/arm64/mod.rs @@ -8,11 +8,8 @@ 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); + let has_lse = setting.add_bool("has_lse", "Has Large System Extensions support.", "", false); setting.add_predicate("use_lse", predicate!(has_lse)); setting.build() diff --git a/cranelift/codegen/meta/src/isa/riscv/mod.rs b/cranelift/codegen/meta/src/isa/riscv/mod.rs index 801e61a3d2..49f26391ce 100644 --- a/cranelift/codegen/meta/src/isa/riscv/mod.rs +++ b/cranelift/codegen/meta/src/isa/riscv/mod.rs @@ -17,33 +17,39 @@ fn define_settings(shared: &SettingGroup) -> SettingGroup { let supports_m = setting.add_bool( "supports_m", "CPU supports the 'M' extension (mul/div)", + "", false, ); let supports_a = setting.add_bool( "supports_a", "CPU supports the 'A' extension (atomics)", + "", false, ); let supports_f = setting.add_bool( "supports_f", "CPU supports the 'F' extension (float)", + "", false, ); let supports_d = setting.add_bool( "supports_d", "CPU supports the 'D' extension (double)", + "", false, ); let enable_m = setting.add_bool( "enable_m", "Enable the use of 'M' instructions if available", + "", true, ); setting.add_bool( "enable_e", "Enable the 'RV32E' instruction set with only 16 registers", + "", false, ); diff --git a/cranelift/codegen/meta/src/isa/x86/settings.rs b/cranelift/codegen/meta/src/isa/x86/settings.rs index 2540ac2065..70b829787d 100644 --- a/cranelift/codegen/meta/src/isa/x86/settings.rs +++ b/cranelift/codegen/meta/src/isa/x86/settings.rs @@ -3,41 +3,78 @@ 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); - let has_sse41 = settings.add_bool("has_sse41", "SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]", false); - let has_sse42 = settings.add_bool("has_sse42", "SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]", false); - let has_avx = settings.add_bool("has_avx", "AVX: CPUID.01H:ECX.AVX[bit 28]", false); - let has_avx2 = settings.add_bool("has_avx2", "AVX2: CPUID.07H:EBX.AVX2[bit 5]", false); + let has_sse3 = settings.add_bool( + "has_sse3", + "Has support for SSE3.", + "SSE3: CPUID.01H:ECX.SSE3[bit 0]", + false, + ); + let has_ssse3 = settings.add_bool( + "has_ssse3", + "Has support for SSSE3.", + "SSSE3: CPUID.01H:ECX.SSSE3[bit 9]", + false, + ); + let has_sse41 = settings.add_bool( + "has_sse41", + "Has support for SSE4.1.", + "SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]", + false, + ); + let has_sse42 = settings.add_bool( + "has_sse42", + "Has support for SSE4.2.", + "SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]", + false, + ); + let has_avx = settings.add_bool( + "has_avx", + "Has support for AVX.", + "AVX: CPUID.01H:ECX.AVX[bit 28]", + false, + ); + let has_avx2 = settings.add_bool( + "has_avx2", + "Has support for AVX2.", + "AVX2: CPUID.07H:EBX.AVX2[bit 5]", + false, + ); let has_avx512dq = settings.add_bool( "has_avx512dq", + "Has support for AVX512DQ.", "AVX512DQ: CPUID.07H:EBX.AVX512DQ[bit 17]", false, ); let has_avx512vl = settings.add_bool( "has_avx512vl", + "Has support for AVX512VL.", "AVX512VL: CPUID.07H:EBX.AVX512VL[bit 31]", false, ); let has_avx512f = settings.add_bool( "has_avx512f", + "Has support for AVX512F.", "AVX512F: CPUID.07H:EBX.AVX512F[bit 16]", false, ); - let has_popcnt = settings.add_bool("has_popcnt", "POPCNT: CPUID.01H:ECX.POPCNT[bit 23]", false); + let has_popcnt = settings.add_bool( + "has_popcnt", + "Has support for POPCNT.", + "POPCNT: CPUID.01H:ECX.POPCNT[bit 23]", + false, + ); // CPUID.(EAX=07H, ECX=0H):EBX let has_bmi1 = settings.add_bool( "has_bmi1", + "Has support for BMI1.", "BMI1: CPUID.(EAX=07H, ECX=0H):EBX.BMI1[bit 3]", false, ); let has_bmi2 = settings.add_bool( "has_bmi2", + "Has support for BMI2.", "BMI2: CPUID.(EAX=07H, ECX=0H):EBX.BMI2[bit 8]", false, ); @@ -45,6 +82,7 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup { // CPUID.EAX=80000001H:ECX let has_lzcnt = settings.add_bool( "has_lzcnt", + "Has support for LZCNT.", "LZCNT: CPUID.EAX=80000001H:ECX.LZCNT[bit 5]", false, ); @@ -107,21 +145,40 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup { // Presets corresponding to x86 CPUs. - settings.add_preset("baseline", preset!()); + settings.add_preset( + "baseline", + "A baseline preset with no extensions enabled.", + preset!(), + ); let nehalem = settings.add_preset( "nehalem", + "Nehalem microarchitecture.", preset!(has_sse3 && has_ssse3 && has_sse41 && has_sse42 && has_popcnt), ); let haswell = settings.add_preset( "haswell", + "Haswell microarchitecture.", preset!(nehalem && has_bmi1 && has_bmi2 && has_lzcnt), ); - let broadwell = settings.add_preset("broadwell", preset!(haswell)); - let skylake = settings.add_preset("skylake", preset!(broadwell)); - let cannonlake = settings.add_preset("cannonlake", preset!(skylake)); - settings.add_preset("icelake", preset!(cannonlake)); + let broadwell = settings.add_preset( + "broadwell", + "Broadwell microarchitecture.", + preset!(haswell), + ); + let skylake = settings.add_preset("skylake", "Skylake microarchitecture.", preset!(broadwell)); + let cannonlake = settings.add_preset( + "cannonlake", + "Canon Lake microarchitecture.", + preset!(skylake), + ); + settings.add_preset( + "icelake", + "Ice Lake microarchitecture.", + preset!(cannonlake), + ); settings.add_preset( "znver1", + "Zen (first generation) microarchitecture.", preset!( has_sse3 && has_ssse3 diff --git a/cranelift/codegen/meta/src/shared/settings.rs b/cranelift/codegen/meta/src/shared/settings.rs index 2acc34118d..2233e85dbc 100644 --- a/cranelift/codegen/meta/src/shared/settings.rs +++ b/cranelift/codegen/meta/src/shared/settings.rs @@ -5,29 +5,29 @@ pub(crate) fn define() -> SettingGroup { settings.add_enum( "regalloc", - r#"Register allocator to use with the MachInst backend. + "Register allocator to use with the MachInst backend.", + r#" + This selects the register allocator as an option among those offered by the `regalloc.rs` + crate. Please report register allocation bugs to the maintainers of this crate whenever + possible. - This selects the register allocator as an option among those offered by the `regalloc.rs` - crate. Please report register allocation bugs to the maintainers of this crate whenever - possible. + Note: this only applies to target that use the MachInst backend. As of 2020-04-17, this + means the x86_64 backend doesn't use this yet. - Note: this only applies to target that use the MachInst backend. As of 2020-04-17, this - means the x86_64 backend doesn't use this yet. + Possible values: - Possible values: - - - `backtracking` is a greedy, backtracking register allocator as implemented in - Spidermonkey's optimizing tier IonMonkey. It may take more time to allocate registers, but - it should generate better code in general, resulting in better throughput of generated - code. - - `backtracking_checked` is the backtracking allocator with additional self checks that may - take some time to run, and thus these checks are disabled by default. - - `experimental_linear_scan` is an experimental linear scan allocator. It may take less - time to allocate registers, but generated code's quality may be inferior. As of - 2020-04-17, it is still experimental and it should not be used in production settings. - - `experimental_linear_scan_checked` is the linear scan allocator with additional self - checks that may take some time to run, and thus these checks are disabled by default. - "#, + - `backtracking` is a greedy, backtracking register allocator as implemented in + Spidermonkey's optimizing tier IonMonkey. It may take more time to allocate registers, but + it should generate better code in general, resulting in better throughput of generated + code. + - `backtracking_checked` is the backtracking allocator with additional self checks that may + take some time to run, and thus these checks are disabled by default. + - `experimental_linear_scan` is an experimental linear scan allocator. It may take less + time to allocate registers, but generated code's quality may be inferior. As of + 2020-04-17, it is still experimental and it should not be used in production settings. + - `experimental_linear_scan_checked` is the linear scan allocator with additional self + checks that may take some time to run, and thus these checks are disabled by default. + "#, vec![ "backtracking", "backtracking_checked", @@ -38,24 +38,23 @@ pub(crate) fn define() -> SettingGroup { settings.add_enum( "opt_level", + "Optimization level for generated code.", r#" - Optimization level: + Supported levels: - - none: Minimise compile time by disabling most optimizations. - - speed: Generate the fastest possible code - - speed_and_size: like "speed", but also perform transformations - aimed at reducing code size. + - `none`: Minimise compile time by disabling most optimizations. + - `speed`: Generate the fastest possible code + - `speed_and_size`: like "speed", but also perform transformations aimed at reducing code size. "#, vec!["none", "speed", "speed_and_size"], ); settings.add_bool( "enable_verifier", + "Run the Cranelift IR verifier at strategic times during compilation.", r#" - Run the Cranelift IR verifier at strategic times during compilation. - - This makes compilation slower but catches many bugs. The verifier is always enabled by - default, which is useful during development. + This makes compilation slower but catches many bugs. The verifier is always enabled by + default, which is useful during development. "#, true, ); @@ -65,110 +64,110 @@ pub(crate) fn define() -> SettingGroup { // `colocated` flag on external functions and global values. settings.add_bool( "is_pic", - "Enable Position-Independent Code generation", + "Enable Position-Independent Code generation.", + "", false, ); settings.add_bool( "use_colocated_libcalls", + "Use colocated libcalls.", r#" - Use colocated libcalls. - Generate code that assumes that libcalls can be declared "colocated", meaning they will be defined along with the current function, such that they can use more efficient addressing. - "#, + "#, false, ); settings.add_bool( "avoid_div_traps", + "Generate explicit checks around native division instructions to avoid their trapping.", r#" - Generate explicit checks around native division instructions to avoid - their trapping. - This is primarily used by SpiderMonkey which doesn't install a signal handler for SIGFPE, but expects a SIGILL trap for division by zero. On ISAs like ARM where the native division instructions don't trap, this setting has no effect - explicit checks are always inserted. - "#, + "#, false, ); settings.add_bool( "enable_float", + "Enable the use of floating-point instructions.", r#" - Enable the use of floating-point instructions - Disabling use of floating-point instructions is not yet implemented. - "#, + "#, true, ); settings.add_bool( "enable_nan_canonicalization", + "Enable NaN canonicalization.", r#" - Enable NaN canonicalization - This replaces NaNs with a single canonical value, for users requiring entirely deterministic WebAssembly computation. This is not required by the WebAssembly spec, so it is not enabled by default. - "#, + "#, false, ); settings.add_bool( "enable_pinned_reg", - r#"Enable the use of the pinned register. - - This register is excluded from register allocation, and is completely under the control of - the end-user. It is possible to read it via the get_pinned_reg instruction, and to set it - with the set_pinned_reg instruction. + "Enable the use of the pinned register.", + r#" + This register is excluded from register allocation, and is completely under the control of + the end-user. It is possible to read it via the get_pinned_reg instruction, and to set it + with the set_pinned_reg instruction. "#, false, ); settings.add_bool( "use_pinned_reg_as_heap_base", - r#"Use the pinned register as the heap base. + "Use the pinned register as the heap base.", + r#" + Enabling this requires the enable_pinned_reg setting to be set to true. It enables a custom + legalization of the `heap_addr` instruction so it will use the pinned register as the heap + base, instead of fetching it from a global value. - Enabling this requires the enable_pinned_reg setting to be set to true. It enables a custom - legalization of the `heap_addr` instruction so it will use the pinned register as the heap - base, instead of fetching it from a global value. - - Warning! Enabling this means that the pinned register *must* be maintained to contain the - heap base address at all times, during the lifetime of a function. Using the pinned - register for other purposes when this is set is very likely to cause crashes. + Warning! Enabling this means that the pinned register *must* be maintained to contain the + heap base address at all times, during the lifetime of a function. Using the pinned + register for other purposes when this is set is very likely to cause crashes. "#, false, ); - settings.add_bool("enable_simd", "Enable the use of SIMD instructions.", false); + settings.add_bool( + "enable_simd", + "Enable the use of SIMD instructions.", + "", + false, + ); settings.add_bool( "enable_atomics", "Enable the use of atomic instructions", + "", true, ); settings.add_bool( "enable_safepoints", + "Enable safepoint instruction insertions.", r#" - Enable safepoint instruction insertions. - This will allow the emit_stack_maps() function to insert the safepoint instruction on top of calls and interrupt traps in order to display the live reference values at that point in the program. - "#, + "#, false, ); settings.add_enum( "tls_model", - r#" - Defines the model used to perform TLS accesses. - "#, + "Defines the model used to perform TLS accesses.", + "", vec!["none", "elf_gd", "macho", "coff"], ); @@ -176,9 +175,9 @@ pub(crate) fn define() -> SettingGroup { settings.add_enum( "libcall_call_conv", + "Defines the calling convention to use for LibCalls call expansion.", r#" - Defines the calling convention to use for LibCalls call expansion, - since it may be different from the ISA default calling convention. + This may be different from the ISA default calling convention. The default value is to use the same calling convention as the ISA default calling convention. @@ -202,9 +201,8 @@ pub(crate) fn define() -> SettingGroup { settings.add_num( "baldrdash_prologue_words", + "Number of pointer-sized words pushed by the baldrdash prologue.", r#" - Number of pointer-sized words pushed by the baldrdash prologue. - Functions with the `baldrdash` calling convention don't generate their own prologue and epilogue. They depend on externally generated code that pushes a fixed number of words in the prologue and restores them @@ -213,15 +211,14 @@ pub(crate) fn define() -> SettingGroup { This setting configures the number of pointer-sized words pushed on the stack when the Cranelift-generated code is entered. This includes the pushed return address on x86. - "#, + "#, 0, ); settings.add_bool( "enable_llvm_abi_extensions", + "Enable various ABI extensions defined by LLVM's behavior.", r#" - Enable various ABI extensions defined by LLVM's behavior. - In some cases, LLVM's implementation of an ABI (calling convention) goes beyond a standard and supports additional argument types or behavior. This option instructs Cranelift codegen to follow LLVM's @@ -232,18 +229,18 @@ pub(crate) fn define() -> SettingGroup { registers. The Fastcall implementation otherwise does not support `i128` arguments, and will panic if they are present and this option is not set. - "#, + "#, false, ); settings.add_bool( "unwind_info", + "Generate unwind information.", r#" - Generate unwind info. This increases metadata size and compile time, - but allows for the debugger to trace frames, is needed for GC tracing - that relies on libunwind (such as in Wasmtime), and is - unconditionally needed on certain platforms (such as Windows) that - must always be able to unwind. + This increases metadata size and compile time, but allows for the + debugger to trace frames, is needed for GC tracing that relies on + libunwind (such as in Wasmtime), and is unconditionally needed on + certain platforms (such as Windows) that must always be able to unwind. "#, true, ); @@ -253,6 +250,7 @@ pub(crate) fn define() -> SettingGroup { settings.add_bool( "emit_all_ones_funcaddrs", "Emit not-yet-relocated function addresses as all-ones bit patterns.", + "", false, ); @@ -260,32 +258,27 @@ pub(crate) fn define() -> SettingGroup { settings.add_bool( "enable_probestack", - r#" - Enable the use of stack probes, for calling conventions which support this - functionality. - "#, + "Enable the use of stack probes for supported calling conventions.", + "", true, ); settings.add_bool( "probestack_func_adjusts_sp", - r#" - Set this to true of the stack probe function modifies the stack pointer - itself. - "#, + "Enable if the stack probe adjusts the stack pointer.", + "", false, ); settings.add_num( "probestack_size_log2", + "The log2 of the size of the stack guard region.", r#" - The log2 of the size of the stack guard region. - Stack frames larger than this size will have stack overflow checked by calling the probestack function. The default is 12, which translates to a size of 4096. - "#, + "#, 12, ); @@ -294,6 +287,7 @@ pub(crate) fn define() -> SettingGroup { settings.add_bool( "enable_jump_tables", "Enable the use of jump tables in generated machine code.", + "", true, ); @@ -301,16 +295,15 @@ pub(crate) fn define() -> SettingGroup { settings.add_bool( "enable_heap_access_spectre_mitigation", + "Enable Spectre mitigation on heap bounds checks.", r#" - Enable Spectre mitigation on heap bounds checks. + This is a no-op for any heap that needs no bounds checks; e.g., + if the limit is static and the guard region is large enough that + the index cannot reach past it. - This is a no-op for any heap that needs no bounds checks; e.g., - if the limit is static and the guard region is large enough that - the index cannot reach past it. - - This option is enabled by default because it is highly - recommended for secure sandboxing. The embedder should consider - the security implications carefully before disabling this option. + This option is enabled by default because it is highly + recommended for secure sandboxing. The embedder should consider + the security implications carefully before disabling this option. "#, true, ); diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs index 44fbf564d3..519f061f91 100644 --- a/cranelift/codegen/src/isa/mod.rs +++ b/cranelift/codegen/src/isa/mod.rs @@ -200,6 +200,16 @@ pub struct Builder { } impl Builder { + /// Gets the triple for the builder. + pub fn triple(&self) -> &Triple { + &self.triple + } + + /// Iterates the available settings in the builder. + pub fn iter(&self) -> impl Iterator { + self.setup.iter() + } + /// Combine the ISA-specific settings with the provided ISA-independent settings and allocate a /// fully configured `TargetIsa` trait object. pub fn finish(self, shared_flags: settings::Flags) -> Box { diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index 11c3639d6f..ecdb17050b 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -44,6 +44,34 @@ pub trait Configurable { fn enable(&mut self, name: &str) -> SetResult<()>; } +/// Represents the kind of setting. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SettingKind { + /// The setting is an enumeration. + Enum, + /// The setting is a number. + Num, + /// The setting is a boolean. + Bool, + /// The setting is a preset. + Preset, +} + +/// Represents an available builder setting. +/// +/// This is used for iterating settings in a builder. +#[derive(Clone, Copy, Debug)] +pub struct Setting { + /// The name of the setting. + pub name: &'static str, + /// The description of the setting. + pub description: &'static str, + /// The kind of the setting. + pub kind: SettingKind, + /// The supported values of the setting (for enum values). + pub values: Option<&'static [&'static str]>, +} + /// Collect settings values based on a template. #[derive(Clone, Hash)] pub struct Builder { @@ -66,6 +94,30 @@ impl Builder { self.bytes } + /// Iterates the available settings in the builder. + pub fn iter(&self) -> impl Iterator { + let template = self.template; + + template.descriptors.iter().map(move |d| { + let (kind, values) = match d.detail { + detail::Detail::Enum { last, enumerators } => { + let values = template.enums(last, enumerators); + (SettingKind::Enum, Some(values)) + } + detail::Detail::Num => (SettingKind::Num, None), + detail::Detail::Bool { .. } => (SettingKind::Bool, None), + detail::Detail::Preset => (SettingKind::Preset, None), + }; + + Setting { + name: d.name, + description: d.description, + kind, + values, + } + }) + } + /// Set the value of a single bit. fn set_bit(&mut self, offset: usize, bit: u8, value: bool) { let byte = &mut self.bytes[offset]; @@ -288,6 +340,9 @@ pub mod detail { /// Lower snake-case name of setting as defined in meta. pub name: &'static str, + /// The description of the setting. + pub description: &'static str, + /// Offset of byte containing this setting. pub offset: u32, diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index bbe6c1e1b9..de13ac0e49 100644 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -11,7 +11,7 @@ pub mod ir { pub mod settings { pub use cranelift_codegen::settings::{ - builder, Builder, Configurable, Flags, OptLevel, SetError, + builder, Builder, Configurable, Flags, OptLevel, SetError, Setting, SettingKind, }; } diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index bb3fcf0da6..de0e29c12a 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -271,6 +271,15 @@ impl Module { return Self::deserialize(engine, &binary[COMPILED_MODULE_HEADER.len()..]); } + // Check to see that the config's target matches the host + let target = engine.config().isa_flags.triple(); + if *target != target_lexicon::Triple::host() { + bail!( + "target '{}' specified in the configuration does not match the host", + target + ); + } + const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); cfg_if::cfg_if! { diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 46490227ef..475f2c607b 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::{ - CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, + CompileCommand, ConfigCommand, RunCommand, SettingsCommand, WasmToObjCommand, WastCommand, }; /// Wasmtime WebAssembly Runtime @@ -42,6 +42,8 @@ enum WasmtimeApp { Compile(CompileCommand), /// Runs a WebAssembly module Run(RunCommand), + /// Displays available Cranelift settings for a target. + Settings(SettingsCommand), /// Translates a WebAssembly module to native object file #[structopt(name = "wasm2obj")] WasmToObj(WasmToObjCommand), @@ -56,6 +58,7 @@ impl WasmtimeApp { Self::Config(c) => c.execute(), Self::Compile(c) => c.execute(), Self::Run(c) => c.execute(), + Self::Settings(c) => c.execute(), Self::WasmToObj(c) => c.execute(), Self::Wast(c) => c.execute(), } diff --git a/src/commands.rs b/src/commands.rs index 1f9bc601f9..ebed3d732b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,7 +3,8 @@ mod compile; mod config; mod run; +mod settings; mod wasm2obj; mod wast; -pub use self::{compile::*, config::*, run::*, wasm2obj::*, wast::*}; +pub use self::{compile::*, config::*, run::*, settings::*, wasm2obj::*, wast::*}; diff --git a/src/commands/compile.rs b/src/commands/compile.rs index b6656e1eca..8401dd7705 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -1,7 +1,7 @@ -//! The module that implements the `wasmtime wast` command. +//! The module that implements the `wasmtime compile` command. use crate::CommonOptions; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; use std::fs::{self, File}; use std::io::BufWriter; use std::path::PathBuf; @@ -10,7 +10,7 @@ use structopt::{ StructOpt, }; use target_lexicon::Triple; -use wasmtime::{Config, Engine, Module}; +use wasmtime::{Engine, Module}; lazy_static::lazy_static! { static ref AFTER_HELP: String = { @@ -31,7 +31,7 @@ lazy_static::lazy_static! { \n\ Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ \n \ - wasmtime compile --target x86_64-unknown-linux --skylake foo.wasm\n", + wasmtime compile --target x86_64-unknown-linux --cranelift-enable skylake foo.wasm\n", crate::WASM_FEATURES.as_str() ) }; @@ -57,90 +57,6 @@ pub struct CompileCommand { #[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, @@ -167,8 +83,6 @@ impl CompileCommand { 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() { @@ -191,48 +105,6 @@ impl CompileCommand { 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)] @@ -285,19 +157,32 @@ mod test { let command = CompileCommand::from_iter_safe(vec![ "compile", "--disable-logging", - "--sse3", - "--ssse3", - "--sse41", - "--sse42", - "--avx", - "--avx2", - "--avx512dq", - "--avx512vl", - "--avx512f", - "--popcnt", - "--bmi1", - "--bmi2", - "--lzcnt", + "--cranelift-enable", + "has_sse3", + "--cranelift-enable", + "has_ssse3", + "--cranelift-enable", + "has_sse41", + "--cranelift-enable", + "has_sse42", + "--cranelift-enable", + "has_avx", + "--cranelift-enable", + "has_avx2", + "--cranelift-enable", + "has_avx512dq", + "--cranelift-enable", + "has_avx512vl", + "--cranelift-enable", + "has_avx512f", + "--cranelift-enable", + "has_popcnt", + "--cranelift-enable", + "has_bmi1", + "--cranelift-enable", + "has_bmi2", + "--cranelift-enable", + "has_lzcnt", "-o", output_path.to_str().unwrap(), input_path.to_str().unwrap(), @@ -321,7 +206,8 @@ mod test { let command = CompileCommand::from_iter_safe(vec![ "compile", "--disable-logging", - "--lse", + "--cranelift-enable", + "has_lse", "-o", output_path.to_str().unwrap(), input_path.to_str().unwrap(), @@ -334,30 +220,28 @@ mod test { #[cfg(target_arch = "x86_64")] #[test] - fn test_incompatible_flags_compile() -> Result<()> { + fn test_unsupported_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![ + // aarch64 flags should not be supported + let command = CompileCommand::from_iter_safe(vec![ "compile", "--disable-logging", - "--sse3", - "--lse", + "--cranelift-enable", + "has_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")); - } - } + ])?; + + assert_eq!( + command.execute().unwrap_err().to_string(), + "No existing setting named 'has_lse'" + ); Ok(()) } @@ -372,17 +256,18 @@ mod test { let output_path = NamedTempFile::new()?.into_temp_path(); for preset in &[ - "--nehalem", - "--haswell", - "--broadwell", - "--skylake", - "--cannonlake", - "--icelake", - "--znver1", + "nehalem", + "haswell", + "broadwell", + "skylake", + "cannonlake", + "icelake", + "znver1", ] { let command = CompileCommand::from_iter_safe(vec![ "compile", "--disable-logging", + "--cranelift-enable", preset, "-o", output_path.to_str().unwrap(), @@ -392,24 +277,6 @@ mod test { 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/settings.rs b/src/commands/settings.rs new file mode 100644 index 0000000000..e791c7ae72 --- /dev/null +++ b/src/commands/settings.rs @@ -0,0 +1,103 @@ +//! The module that implements the `wasmtime settings` command. + +use anyhow::{anyhow, Result}; +use std::str::FromStr; +use structopt::StructOpt; +use wasmtime_environ::settings::{self, Setting, SettingKind}; +use wasmtime_jit::native; + +/// Displays available Cranelift settings for a target. +#[derive(StructOpt)] +#[structopt(name = "run")] +pub struct SettingsCommand { + /// The target triple to get the settings for; defaults to the host triple. + #[structopt(long, value_name = "TARGET")] + target: Option, +} + +impl SettingsCommand { + /// Executes the command. + pub fn execute(self) -> Result<()> { + let settings = match &self.target { + Some(target) => { + native::lookup(target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?)? + } + None => native::builder(), + }; + + let mut enums = (Vec::new(), 0); + let mut nums = (Vec::new(), 0); + let mut bools = (Vec::new(), 0); + let mut presets = (Vec::new(), 0); + + for setting in settings.iter() { + let (collection, max) = match setting.kind { + SettingKind::Enum => &mut enums, + SettingKind::Num => &mut nums, + SettingKind::Bool => &mut bools, + SettingKind::Preset => &mut presets, + }; + + if setting.name.len() > *max { + *max = setting.name.len(); + } + + collection.push(setting); + } + + if enums.0.is_empty() && nums.0.is_empty() && bools.0.is_empty() && presets.0.is_empty() { + println!("Target '{}' has no settings.", settings.triple()); + return Ok(()); + } + + println!("Cranelift settings for target '{}':", settings.triple()); + + if !enums.0.is_empty() { + println!(); + Self::print_settings("Enum settings:", enums.0, enums.1); + } + + if !nums.0.is_empty() { + println!(); + Self::print_settings("Numerical settings:", nums.0, nums.1); + } + + if !bools.0.is_empty() { + println!(); + Self::print_settings("Boolean settings:", bools.0, bools.1); + } + + if !presets.0.is_empty() { + println!(); + Self::print_settings("Presets:", presets.0, presets.1); + } + + if self.target.is_none() { + let isa = settings.finish(settings::Flags::new(settings::builder())); + println!(); + println!("Settings enabled for this host:"); + + for flag in isa.enabled_isa_flags() { + println!(" - {}", flag); + } + } + + Ok(()) + } + + fn print_settings(header: &str, settings: impl IntoIterator, width: usize) { + println!("{}", header); + for setting in settings { + println!( + " {:width$} {}{}", + setting.name, + setting.description, + setting + .values + .map(|v| format!(" Supported values: {}.", v.join(", "))) + .unwrap_or("".to_string()), + width = width + 2 + ); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a89a7e7835..3485b62889 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,12 +212,19 @@ struct CommonOptions { )] opt_level: Option, - /// Cranelift common flags to set. - #[structopt(long = "cranelift-set", value_name = "NAME=VALUE", parse(try_from_str = parse_cranelift_flag))] + /// Set a Cranelift setting to a given value. + /// Use `wasmtime settings` to list Cranelift settings for a target. + #[structopt(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))] cranelift_set: Vec<(String, String)>, - /// The Cranelift boolean setting or preset to enable. - #[structopt(long, value_name = "SETTING")] + /// Enable a Cranelift boolean setting or preset. + /// Use `wasmtime settings` to list Cranelift settings for a target. + #[structopt( + long, + value_name = "SETTING", + number_of_values = 1, + verbatim_doc_comment + )] cranelift_enable: Vec, /// Maximum size in bytes of wasm memory before it becomes dynamically diff --git a/tests/all/module.rs b/tests/all/module.rs index 0f811ac085..08996debac 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -2,6 +2,23 @@ use anyhow::Result; use std::io::BufWriter; use wasmtime::*; +#[test] +fn checks_incompatible_target() -> Result<()> { + let mut target = target_lexicon::Triple::host(); + target.operating_system = target_lexicon::OperatingSystem::Unknown; + match Module::new( + &Engine::new(Config::new().target(&target.to_string())?)?, + "(module)", + ) { + Ok(_) => unreachable!(), + Err(e) => assert!(e + .to_string() + .contains("configuration does not match the host")), + } + + Ok(()) +} + #[test] fn caches_across_engines() { let c = Config::new(); From d1313b129156307fb07ab42e239bcf4428050930 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 31 Mar 2021 23:46:30 -0700 Subject: [PATCH 08/12] Code review feedback. * Move `Module::compile` to `Engine::precompile_module`. * Remove `Module::deserialize` method. * Make `Module::serialize` the same format as `Engine::precompile_module`. * Make `Engine::precompile_module` return a `Vec`. * Move the remaining serialization-related code to `serialization.rs`. --- RELEASES.md | 15 +-- crates/c-api/src/module.rs | 11 +- crates/wasmtime/src/engine.rs | 28 +++++ crates/wasmtime/src/module.rs | 123 +++----------------- crates/wasmtime/src/module/serialization.rs | 70 ++++++++++- examples/serialize.rs | 2 +- src/commands/compile.rs | 10 +- tests/all/module.rs | 15 +-- tests/all/module_linking.rs | 2 +- tests/all/module_serialize.rs | 6 +- 10 files changed, 136 insertions(+), 146 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index dc19448778..59f5297a87 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,14 +6,13 @@ ### Added -* The `wasmtime compile` command was added to support AOT compilation of Wasm - modules. +* Added the `wasmtime compile` command to support AOT compilation of Wasm modules. -* The `Module::compile` method was added to support AOT compilation of a module. +* Added the `Engine::precompile_module` method to support AOT module compilation. * Added the `Config::target` method to change the compilation target of the - configuration. This can be used in conjunction with `Module::compile` to target - a different host triple than the current one. + configuration. This can be used in conjunction with `Engine::precompile_module` + to target a different host triple than the current one. * Added the `Config::cranelift_flag_enable` method to enable setting Cranelift boolean flags or presets in a config. @@ -26,6 +25,8 @@ singular `--wasm-features` option. The previous options are still supported, but are not displayed in help text. +* Breaking: `Module::deserialize` has been removed in favor of `Module::new`. + * Breaking: `Config::cranelift_clear_cpu_flags` was removed. Use `Config::target` to clear the CPU flags for the host's target. @@ -42,10 +43,6 @@ * Breaking: the CLI option `--enable-bulk-memory=false` has been changed to `--wasm-features=-bulk-memory`. -* 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. diff --git a/crates/c-api/src/module.rs b/crates/c-api/src/module.rs index c31f125b70..1757191026 100644 --- a/crates/c-api/src/module.rs +++ b/crates/c-api/src/module.rs @@ -185,13 +185,10 @@ pub extern "C" fn wasmtime_module_deserialize( binary: &wasm_byte_vec_t, ret: &mut *mut wasm_module_t, ) -> Option> { - handle_result( - Module::deserialize(&engine.engine, binary.as_slice()), - |module| { - let module = Box::new(wasm_module_t::new(module)); - *ret = Box::into_raw(module); - }, - ) + handle_result(Module::new(&engine.engine, binary.as_slice()), |module| { + let module = Box::new(wasm_module_t::new(module)); + *ret = Box::into_raw(module); + }) } #[no_mangle] diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index eab429d509..28391d298b 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -78,6 +78,34 @@ impl Engine { pub fn same(a: &Engine, b: &Engine) -> bool { Arc::ptr_eq(&a.inner, &b.inner) } + + /// 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. + /// + /// [binary]: https://webassembly.github.io/spec/core/binary/index.html + /// [text]: https://webassembly.github.io/spec/core/text/index.html + pub fn precompile_module(&self, bytes: &[u8]) -> Result> { + const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); + + #[cfg(feature = "wat")] + let bytes = wat::parse_bytes(&bytes)?; + + let (_, artifacts, types) = wasmtime_jit::CompilationArtifacts::build( + &self.inner.compiler, + &bytes, + USE_PAGED_MEM_INIT, + )?; + + crate::module::SerializedModule::from_artifacts(&self.inner.compiler, &artifacts, &types) + .to_bytes() + } } impl Default for Engine { diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index de0e29c12a..cffcc04278 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1,9 +1,7 @@ use crate::types::{ExportType, ExternType, ImportType}; use crate::{Engine, ModuleType}; use anyhow::{bail, Context, Result}; -use bincode::Options; use std::fs; -use std::io::Write; use std::path::Path; use std::sync::Arc; use wasmparser::Validator; @@ -15,9 +13,7 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; mod serialization; -use serialization::SerializedModule; - -const COMPILED_MODULE_HEADER: &[u8] = b"\0wasmtime-aot"; +pub use serialization::SerializedModule; /// A compiled WebAssembly module, ready to be instantiated. /// @@ -111,14 +107,16 @@ struct ModuleInner { impl Module { /// Creates a new WebAssembly `Module` from the given in-memory `bytes`. /// - /// The `bytes` provided must be in one of three formats: + /// The `bytes` provided must be in one of the following 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. - /// * A module compiled with [`Module::compile`] or the `wasmtime compile` command. + /// * A module serialized with [`Module::serialize`]. + /// * A module compiled with [`Engine::precompile_module`] 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 @@ -177,8 +175,9 @@ 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()..]); + + if let Some(module) = SerializedModule::from_bytes(bytes)? { + return module.into_module(engine); } #[cfg(feature = "wat")] @@ -267,8 +266,8 @@ 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()..]); + if let Some(module) = SerializedModule::from_bytes(binary)? { + return module.into_module(engine); } // Check to see that the config's target matches the host @@ -344,41 +343,6 @@ 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)?; - Self::serialize_module( - &SerializedModule::from_artifacts(engine.compiler(), &artifacts, &types), - output, - ) - } - /// Returns the type signature of this module. pub fn ty(&self) -> ModuleType { let mut sig = ModuleType::new(); @@ -396,58 +360,12 @@ impl Module { sig } - /// Serialize compilation artifacts to the buffer. See also `deserialize`. - pub fn serialize(&self) -> Result> { - let mut buffer = Vec::new(); - Self::serialize_module(&SerializedModule::new(self), &mut buffer)?; - Ok(buffer) - } - - fn serialize_module(module: &SerializedModule, mut output: impl Write) -> Result<()> { - // Preface the data with a version so we can do a version check independent - // of the serialized data. - let version = env!("CARGO_PKG_VERSION"); - assert!( - version.len() < 256, - "package version must be less than 256 bytes" - ); - output.write(&[version.len() as u8])?; - output.write_all(version.as_bytes())?; - bincode_options().serialize_into(output, module)?; - Ok(()) - } - - /// 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 - /// package version. + /// Serialize the module to a vector of bytes. /// - /// 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 corruptions. All responsibly of signing and its - /// verification falls on the embedder. - pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { - if serialized.is_empty() { - bail!("serialized data data is empty"); - } - - let version_len = serialized[0] as usize; - if serialized.len() < version_len + 1 { - bail!("serialized data is malformed"); - } - - let version = std::str::from_utf8(&serialized[1..1 + version_len])?; - if version != env!("CARGO_PKG_VERSION") { - bail!( - "Module was compiled with incompatible Wasmtime version '{}'", - version - ); - } - - bincode_options() - .deserialize::>(&serialized[1 + version_len..]) - .context("Deserialize compilation artifacts")? - .into_module(engine) + /// Use `Module::new` or `Module::from_binary` to create the module + /// from the bytes. + pub fn serialize(&self) -> Result> { + SerializedModule::new(self).to_bytes() } /// Creates a submodule `Module` value from the specified parameters. @@ -732,17 +650,6 @@ impl Module { } } -fn bincode_options() -> impl Options { - // Use a variable-length integer encoding instead of fixed length. The - // module shown on #2318 gets compressed from ~160MB to ~110MB simply using - // this, presumably because there's a lot of 8-byte integers which generally - // have small values. Local testing shows that the deserialization - // performance, while higher, is in the few-percent range. For huge size - // savings this seems worthwhile to lose a small percentage of - // deserialization performance. - bincode::DefaultOptions::new().with_varint_encoding() -} - fn _assert_send_sync() { fn _assert() {} _assert::(); diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 3daa7b9259..27936ffe9d 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -2,7 +2,8 @@ use super::ModuleInner; use crate::{Engine, Module, OptLevel}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use bincode::Options; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -14,6 +15,19 @@ use wasmtime_jit::{ CompilationArtifacts, CompilationStrategy, CompiledModule, Compiler, TypeTables, }; +const HEADER: &[u8] = b"\0wasmtime-aot"; + +fn bincode_options() -> impl Options { + // Use a variable-length integer encoding instead of fixed length. The + // module shown on #2318 gets compressed from ~160MB to ~110MB simply using + // this, presumably because there's a lot of 8-byte integers which generally + // have small values. Local testing shows that the deserialization + // performance, while higher, is in the few-percent range. For huge size + // savings this seems worthwhile to lose a small percentage of + // deserialization performance. + bincode::DefaultOptions::new().with_varint_encoding() +} + // This exists because `wasmparser::WasmFeatures` isn't serializable #[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)] struct WasmFeatures { @@ -273,6 +287,60 @@ impl<'a> SerializedModule<'a> { } } + pub fn to_bytes(&self) -> Result> { + use std::io::Write; + + let mut bytes = Vec::new(); + + bytes.write_all(HEADER)?; + + // Preface the data with a version so we can do a version check independent + // of the serialized data. + let version = env!("CARGO_PKG_VERSION"); + assert!( + version.len() < 256, + "package version must be less than 256 bytes" + ); + bytes.write(&[version.len() as u8])?; + + bytes.write_all(version.as_bytes())?; + + bincode_options().serialize_into(&mut bytes, self)?; + + Ok(bytes) + } + + pub fn from_bytes(bytes: &[u8]) -> Result> { + if !bytes.starts_with(HEADER) { + return Ok(None); + } + + let bytes = &bytes[HEADER.len()..]; + + if bytes.is_empty() { + bail!("serialized data data is empty"); + } + + let version_len = bytes[0] as usize; + if bytes.len() < version_len + 1 { + bail!("serialized data is malformed"); + } + + let version = std::str::from_utf8(&bytes[1..1 + version_len])?; + if version != env!("CARGO_PKG_VERSION") { + bail!( + "Module was compiled with incompatible Wasmtime version '{}'", + version + ); + } + + Ok(Some( + bincode_options() + .deserialize::>(&bytes[1 + version_len..]) + .context("deserialize compilation artifacts")?, + )) + } + fn check_triple(&self, isa: &dyn TargetIsa) -> Result<()> { let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; diff --git a/examples/serialize.rs b/examples/serialize.rs index dd30b47a98..3cd709e748 100644 --- a/examples/serialize.rs +++ b/examples/serialize.rs @@ -31,7 +31,7 @@ fn deserialize(buffer: &[u8]) -> Result<()> { // Compile the wasm binary into an in-memory instance of a `Module`. println!("Deserialize module..."); - let module = Module::deserialize(store.engine(), buffer)?; + let module = Module::new(store.engine(), buffer)?; // Here we handle the imports of the module, which in this case is our // `HelloCallback` type and its associated implementation of `Callback. diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 8401dd7705..21f0cedc76 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -2,15 +2,14 @@ use crate::CommonOptions; use anyhow::{bail, Context, Result}; -use std::fs::{self, File}; -use std::io::BufWriter; +use std::fs; use std::path::PathBuf; use structopt::{ clap::{AppSettings, ArgGroup}, StructOpt, }; use target_lexicon::Triple; -use wasmtime::{Engine, Module}; +use wasmtime::Engine; lazy_static::lazy_static! { static ref AFTER_HELP: String = { @@ -100,8 +99,7 @@ impl CompileCommand { output }); - let mut writer = BufWriter::new(File::create(&output)?); - Module::compile(&engine, &input, &mut writer)?; + fs::write(output, engine.precompile_module(&input)?)?; Ok(()) } @@ -112,7 +110,7 @@ mod test { use super::*; use std::io::Write; use tempfile::NamedTempFile; - use wasmtime::{Instance, Store}; + use wasmtime::{Instance, Module, Store}; #[test] fn test_successful_compile() -> Result<()> { diff --git a/tests/all/module.rs b/tests/all/module.rs index 08996debac..279c91e829 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use std::io::BufWriter; use wasmtime::*; #[test] @@ -28,18 +27,18 @@ fn caches_across_engines() { .serialize() .unwrap(); - let res = Module::deserialize(&Engine::new(&Config::new()).unwrap(), &bytes); + let res = Module::new(&Engine::new(&Config::new()).unwrap(), &bytes); assert!(res.is_ok()); // differ in shared cranelift flags - let res = Module::deserialize( + let res = Module::new( &Engine::new(Config::new().cranelift_nan_canonicalization(true)).unwrap(), &bytes, ); assert!(res.is_err()); // differ in cranelift settings - let res = Module::deserialize( + let res = Module::new( &Engine::new(Config::new().cranelift_opt_level(OptLevel::None)).unwrap(), &bytes, ); @@ -47,7 +46,7 @@ fn caches_across_engines() { // Missing required cpu flags if cfg!(target_arch = "x86_64") { - let res = Module::deserialize( + let res = Module::new( &Engine::new( Config::new() .target(&target_lexicon::Triple::host().to_string()) @@ -63,14 +62,10 @@ fn caches_across_engines() { #[test] fn aot_compiles() -> Result<()> { let engine = Engine::default(); - let mut writer = BufWriter::new(Vec::new()); - Module::compile( - &engine, + let bytes = engine.precompile_module( "(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); diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs index b010573c6e..101655eff3 100644 --- a/tests/all/module_linking.rs +++ b/tests/all/module_linking.rs @@ -39,7 +39,7 @@ fn compile() -> Result<()> { assert_eq!(m.imports().len(), 0); assert_eq!(m.exports().len(), 0); let bytes = m.serialize()?; - Module::deserialize(&engine, &bytes)?; + Module::new(&engine, &bytes)?; assert_eq!(m.imports().len(), 0); assert_eq!(m.exports().len(), 0); Ok(()) diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs index dab73f6775..abb0f7752d 100644 --- a/tests/all/module_serialize.rs +++ b/tests/all/module_serialize.rs @@ -7,7 +7,7 @@ fn serialize(engine: &Engine, wat: &'static str) -> Result> { } fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result { - let module = Module::deserialize(store.engine(), buffer)?; + let module = Module::new(store.engine(), buffer)?; Ok(Instance::new(&store, &module, &[])?) } @@ -15,9 +15,9 @@ fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result fn test_version_mismatch() -> Result<()> { let engine = Engine::default(); let mut buffer = serialize(&engine, "(module)")?; - buffer[1] = 'x' as u8; + buffer[13 /* header length */ + 1 /* version length */] = 'x' as u8; - match Module::deserialize(&engine, &buffer) { + match Module::new(&engine, &buffer) { Ok(_) => bail!("expected deserialization to fail"), Err(e) => assert_eq!( e.to_string(), From 9e7d2fed9872e66afd1534b8267927c738fa5eb0 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 1 Apr 2021 11:22:01 -0700 Subject: [PATCH 09/12] Sort output in `wasmtime settings`. This commit sorts the settings output by the `wasmtime settings` command. --- docs/cli-options.md | 11 ++++++++++ src/commands/settings.rs | 43 +++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/cli-options.md b/docs/cli-options.md index 5701cb48a6..be78272a78 100644 --- a/docs/cli-options.md +++ b/docs/cli-options.md @@ -95,3 +95,14 @@ $ wasmtime foo.cwasm AOT-compiled modules can be run from hosts that are compatible with the target environment of the AOT-completed module. + +## `settings` + +This subcommand is used to print the available Cranelift settings for a given target. + +When run without options, it will print the settings for the host target and also +display what Cranelift settings are inferred for the host: + +```sh +$ wasmtime settings +``` diff --git a/src/commands/settings.rs b/src/commands/settings.rs index e791c7ae72..63a7beff92 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -25,13 +25,13 @@ impl SettingsCommand { None => native::builder(), }; - let mut enums = (Vec::new(), 0); - let mut nums = (Vec::new(), 0); - let mut bools = (Vec::new(), 0); - let mut presets = (Vec::new(), 0); + let mut enums = (Vec::new(), 0, "Enum settings:"); + let mut nums = (Vec::new(), 0, "Numerical settings:"); + let mut bools = (Vec::new(), 0, "Boolean settings:"); + let mut presets = (Vec::new(), 0, "Presets:"); for setting in settings.iter() { - let (collection, max) = match setting.kind { + let (collection, max, _) = match setting.kind { SettingKind::Enum => &mut enums, SettingKind::Num => &mut nums, SettingKind::Bool => &mut bools, @@ -52,40 +52,33 @@ impl SettingsCommand { println!("Cranelift settings for target '{}':", settings.triple()); - if !enums.0.is_empty() { - println!(); - Self::print_settings("Enum settings:", enums.0, enums.1); - } + for (collection, max, header) in &mut [enums, nums, bools, presets] { + if collection.is_empty() { + continue; + } - if !nums.0.is_empty() { + collection.sort_by_key(|k| k.name); println!(); - Self::print_settings("Numerical settings:", nums.0, nums.1); - } - - if !bools.0.is_empty() { - println!(); - Self::print_settings("Boolean settings:", bools.0, bools.1); - } - - if !presets.0.is_empty() { - println!(); - Self::print_settings("Presets:", presets.0, presets.1); + Self::print_settings(header, collection, *max); } if self.target.is_none() { let isa = settings.finish(settings::Flags::new(settings::builder())); println!(); - println!("Settings enabled for this host:"); + println!("Settings inferred for the current host:"); - for flag in isa.enabled_isa_flags() { - println!(" - {}", flag); + let mut enabled = isa.enabled_isa_flags(); + enabled.sort(); + + for flag in enabled { + println!(" {}", flag); } } Ok(()) } - fn print_settings(header: &str, settings: impl IntoIterator, width: usize) { + fn print_settings(header: &str, settings: &[Setting], width: usize) { println!("{}", header); for setting in settings { println!( From 3da03bcfcfa13fcb051d509906fbd8d0411300c9 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 1 Apr 2021 11:48:33 -0700 Subject: [PATCH 10/12] Code review feedback. * Expand doc comment on `Engine::precompile_module`. * Add FIXME comment regarding a future ISA flag compatibility check before doing a JIT from `Module::from_binary`. * Remove no-longer-needed CLI groups from the `compile` command. --- crates/wasmtime/src/engine.rs | 9 +++++++++ crates/wasmtime/src/module.rs | 4 ++++ src/commands/compile.rs | 9 +-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 28391d298b..f7d9c4eaa4 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -89,6 +89,15 @@ impl Engine { /// If this is supplied then the text format will be parsed before validation. /// Note that the `wat` feature is enabled by default. /// + /// This method may be used to compile a module for use with a different target + /// host. The output of this method may be used with [`Module::new`](crate::Module::new) + /// on hosts compatible with the [`Config`] associated with this [`Engine`]. + /// + /// The output of this method is safe to send to another host machine for later + /// execution. As the output is already a compiled module, translation and code + /// generation will be skipped and this will improve the performance of constructing + /// a [`Module`](crate::Module) from the output of this method. + /// /// [binary]: https://webassembly.github.io/spec/core/binary/index.html /// [text]: https://webassembly.github.io/spec/core/text/index.html pub fn precompile_module(&self, bytes: &[u8]) -> Result> { diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index cffcc04278..8408d2af97 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -279,6 +279,10 @@ impl Module { ); } + // FIXME: we may want to validate that the ISA flags in the config match those that + // would be inferred for the host, otherwise the JIT might produce unrunnable code + // for the features the host's CPU actually has. + const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); cfg_if::cfg_if! { diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 21f0cedc76..7b9a7ebcc2 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -4,10 +4,7 @@ use crate::CommonOptions; use anyhow::{bail, Context, Result}; use std::fs; use std::path::PathBuf; -use structopt::{ - clap::{AppSettings, ArgGroup}, - StructOpt, -}; +use structopt::{clap::AppSettings, StructOpt}; use target_lexicon::Triple; use wasmtime::Engine; @@ -42,10 +39,6 @@ lazy_static::lazy_static! { 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 = AFTER_HELP.as_str() )] pub struct CompileCommand { From 4ad0099da4daf88c0bd77d9bde542b81c5131596 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 1 Apr 2021 12:04:47 -0700 Subject: [PATCH 11/12] Update `wat` crate. Update the `wat` crate to latest version and use `Error::set_path` in `Module::from_file` to properly record the path associated with errors. --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- cranelift/wasm/Cargo.toml | 2 +- crates/fuzzing/Cargo.toml | 2 +- crates/lightbeam/Cargo.toml | 2 +- crates/test-programs/Cargo.toml | 2 +- crates/wasmtime/src/module.rs | 19 ++++++++++++++++--- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2626813a7b..148263def6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3598,9 +3598,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0fa059022c5dabe129f02b429d67086400deb8277f89c975555dacc1dadbcc" +checksum = "8ec280a739b69173e0ffd12c1658507996836ba4e992ed9bc1e5385a0bd72a02" dependencies = [ "wast 35.0.1", ] diff --git a/Cargo.toml b/Cargo.toml index a66027c480..f909d7149f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ anyhow = "1.0.19" target-lexicon = { version = "0.12.0", default-features = false } pretty_env_logger = "0.4.0" file-per-thread-logger = "0.1.1" -wat = "1.0.36" +wat = "1.0.37" libc = "0.2.60" log = "0.4.8" rayon = "1.2.1" diff --git a/cranelift/wasm/Cargo.toml b/cranelift/wasm/Cargo.toml index 636a7c232a..9c1613fa8b 100644 --- a/cranelift/wasm/Cargo.toml +++ b/cranelift/wasm/Cargo.toml @@ -24,7 +24,7 @@ smallvec = "1.6.1" thiserror = "1.0.4" [dev-dependencies] -wat = "1.0.36" +wat = "1.0.37" target-lexicon = "0.12" # Enable the riscv feature for cranelift-codegen, as some tests require it cranelift-codegen = { path = "../codegen", version = "0.72.0", default-features = false, features = ["riscv"] } diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index 30e208d9e8..12e90c3fc5 100644 --- a/crates/fuzzing/Cargo.toml +++ b/crates/fuzzing/Cargo.toml @@ -22,7 +22,7 @@ wasm-smith = "0.4.4" wasmi = "0.7.0" [dev-dependencies] -wat = "1.0.36" +wat = "1.0.37" [features] experimental_x64 = ["wasmtime/experimental_x64"] diff --git a/crates/lightbeam/Cargo.toml b/crates/lightbeam/Cargo.toml index 6734949795..de4a2e5878 100644 --- a/crates/lightbeam/Cargo.toml +++ b/crates/lightbeam/Cargo.toml @@ -28,7 +28,7 @@ wasmparser = "0.77" [dev-dependencies] lazy_static = "1.2" -wat = "1.0.36" +wat = "1.0.37" quickcheck = "1.0.0" anyhow = "1.0" diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 74e16cbd9c..5ff2b9676b 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -20,7 +20,7 @@ pretty_env_logger = "0.4.0" tempfile = "3.1.0" os_pipe = "0.9" anyhow = "1.0.19" -wat = "1.0.36" +wat = "1.0.37" cap-std = "0.13" [features] diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 8408d2af97..374ec6ccd0 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -228,10 +228,23 @@ impl Module { /// # } /// ``` pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { - Self::new( + match Self::new( engine, - &fs::read(file).with_context(|| "failed to read input file")?, - ) + &fs::read(&file).with_context(|| "failed to read input file")?, + ) { + Ok(m) => Ok(m), + Err(e) => { + cfg_if::cfg_if! { + if #[cfg(feature = "wat")] { + let mut e = e.downcast::()?; + e.set_path(file); + bail!(e) + } else { + Err(e) + } + } + } + } } /// Creates a new WebAssembly `Module` from the given in-memory `binary` From 0ddfe97a09e36bc97caab36cd217b69fb76a1ba8 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 1 Apr 2021 19:32:15 -0700 Subject: [PATCH 12/12] Change how flags are stored in serialized modules. This commit changes how both the shared flags and ISA flags are stored in the serialized module to detect incompatibilities when a serialized module is instantiated. It improves the error reporting when a compiled module has mismatched shared flags. --- cranelift/codegen/meta/src/gen_settings.rs | 58 +------ cranelift/codegen/src/isa/aarch64/mod.rs | 13 +- cranelift/codegen/src/isa/aarch64/settings.rs | 2 +- cranelift/codegen/src/isa/arm32/mod.rs | 8 +- cranelift/codegen/src/isa/mod.rs | 9 +- cranelift/codegen/src/isa/riscv/mod.rs | 18 +- cranelift/codegen/src/isa/riscv/settings.rs | 2 +- cranelift/codegen/src/isa/x64/mod.rs | 13 +- cranelift/codegen/src/isa/x64/settings.rs | 2 +- cranelift/codegen/src/isa/x86/mod.rs | 18 +- cranelift/codegen/src/isa/x86/settings.rs | 2 +- cranelift/codegen/src/machinst/adapter.rs | 10 +- cranelift/codegen/src/machinst/mod.rs | 9 +- cranelift/codegen/src/settings.rs | 44 +++++ crates/environ/src/data_structures.rs | 2 +- crates/wasmtime/src/module/serialization.rs | 160 ++++++++++++------ src/commands/settings.rs | 10 +- 17 files changed, 194 insertions(+), 186 deletions(-) diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index 2808c2a777..d7116cac9c 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -70,31 +70,25 @@ fn gen_constructor(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatte fmtln!(fmt, "}"); } -/// Generates the `iter_enabled` function. +/// Generates the `iter` 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.doc_comment("Iterates the setting values."); + fmtln!(fmt, "pub fn iter(&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 {"); + fmtln!(fmt, "let values = 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, "detail::Detail::Preset => return None,"); + fmtln!(fmt, "detail::Detail::Enum { last, enumerators } => Some(TEMPLATE.enums(*last, *enumerators)),"); + fmtln!(fmt, "_ => None"); }); - 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, "Some(Value{ name: d.name, detail: d.detail, values, value: bytes[d.offset as usize] })"); }); fmtln!(fmt, "})"); }); @@ -103,39 +97,6 @@ fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) { 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); @@ -496,7 +457,6 @@ fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) { 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/src/isa/aarch64/mod.rs b/cranelift/codegen/src/isa/aarch64/mod.rs index 9fbf7e60c5..a6892b301d 100644 --- a/cranelift/codegen/src/isa/aarch64/mod.rs +++ b/cranelift/codegen/src/isa/aarch64/mod.rs @@ -7,7 +7,7 @@ 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::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; +use alloc::{boxed::Box, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Aarch64Architecture, Architecture, Triple}; @@ -102,15 +102,8 @@ 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 isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() } fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { diff --git a/cranelift/codegen/src/isa/aarch64/settings.rs b/cranelift/codegen/src/isa/aarch64/settings.rs index a9849c121b..9d3898e7b5 100644 --- a/cranelift/codegen/src/isa/aarch64/settings.rs +++ b/cranelift/codegen/src/isa/aarch64/settings.rs @@ -1,6 +1,6 @@ //! AArch64 Settings. -use crate::settings::{self, detail, Builder}; +use crate::settings::{self, detail, Builder, Value}; use core::fmt; // Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index 50e6f6f59c..832fc46f47 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, string::String, vec::Vec}; +use alloc::{boxed::Box, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Architecture, ArmArchitecture, Triple}; @@ -92,14 +92,10 @@ impl MachBackend for Arm32Backend { &self.flags } - fn enabled_isa_flags(&self) -> Vec { + fn 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 519f061f91..079a39fa8a 100644 --- a/cranelift/codegen/src/isa/mod.rs +++ b/cranelift/codegen/src/isa/mod.rs @@ -63,7 +63,7 @@ use crate::result::CodegenResult; use crate::settings; use crate::settings::SetResult; use crate::timing; -use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; +use alloc::{borrow::Cow, boxed::Box, vec::Vec}; use core::any::Any; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -274,11 +274,8 @@ 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; - /// 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; + /// Get the ISA-dependent flag values that were used to make this trait object. + fn isa_flags(&self) -> Vec; /// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher. fn hash_all_flags(&self, hasher: &mut dyn Hasher); diff --git a/cranelift/codegen/src/isa/riscv/mod.rs b/cranelift/codegen/src/isa/riscv/mod.rs index 9db8b60ffa..2c1ebf1c85 100644 --- a/cranelift/codegen/src/isa/riscv/mod.rs +++ b/cranelift/codegen/src/isa/riscv/mod.rs @@ -15,12 +15,7 @@ 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, ToOwned}, - boxed::Box, - string::String, - vec::Vec, -}; +use alloc::{borrow::Cow, boxed::Box, vec::Vec}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -74,15 +69,8 @@ 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 isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() } fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { diff --git a/cranelift/codegen/src/isa/riscv/settings.rs b/cranelift/codegen/src/isa/riscv/settings.rs index 40aa3bed2b..3da9f491fd 100644 --- a/cranelift/codegen/src/isa/riscv/settings.rs +++ b/cranelift/codegen/src/isa/riscv/settings.rs @@ -1,6 +1,6 @@ //! RISC-V Settings. -use crate::settings::{self, detail, Builder}; +use crate::settings::{self, detail, Builder, Value}; use core::fmt; // Include code generated by `cranelift-codegen/meta/src/gen_settings.rs`. This file contains a diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs index ae8b889fbd..c150c01f23 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::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; +use alloc::{boxed::Box, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse, Reg}; use target_lexicon::Triple; @@ -85,15 +85,8 @@ 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 isa_flags(&self) -> Vec { + self.x64_flags.iter().collect() } fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { diff --git a/cranelift/codegen/src/isa/x64/settings.rs b/cranelift/codegen/src/isa/x64/settings.rs index c5371bb132..501e153b46 100644 --- a/cranelift/codegen/src/isa/x64/settings.rs +++ b/cranelift/codegen/src/isa/x64/settings.rs @@ -1,6 +1,6 @@ //! x86 Settings. -use crate::settings::{self, detail, Builder}; +use crate::settings::{self, detail, Builder, Value}; use core::fmt; // Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a diff --git a/cranelift/codegen/src/isa/x86/mod.rs b/cranelift/codegen/src/isa/x86/mod.rs index c01f8acf01..54efe7fcfd 100644 --- a/cranelift/codegen/src/isa/x86/mod.rs +++ b/cranelift/codegen/src/isa/x86/mod.rs @@ -21,12 +21,7 @@ use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use crate::regalloc; use crate::result::CodegenResult; use crate::timing; -use alloc::{ - borrow::{Cow, ToOwned}, - boxed::Box, - string::String, - vec::Vec, -}; +use alloc::{borrow::Cow, boxed::Box, vec::Vec}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -83,15 +78,8 @@ 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 isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() } fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { diff --git a/cranelift/codegen/src/isa/x86/settings.rs b/cranelift/codegen/src/isa/x86/settings.rs index 2d3a3f6698..f13431c1a2 100644 --- a/cranelift/codegen/src/isa/x86/settings.rs +++ b/cranelift/codegen/src/isa/x86/settings.rs @@ -1,6 +1,6 @@ //! x86 Settings. -use crate::settings::{self, detail, Builder}; +use crate::settings::{self, detail, Builder, Value}; use core::fmt; // Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a diff --git a/cranelift/codegen/src/machinst/adapter.rs b/cranelift/codegen/src/machinst/adapter.rs index a75e7393f6..543084a0b5 100644 --- a/cranelift/codegen/src/machinst/adapter.rs +++ b/cranelift/codegen/src/machinst/adapter.rs @@ -5,7 +5,7 @@ use crate::ir; use crate::isa::{EncInfo, Encoding, Encodings, Legalize, RegClass, RegInfo, TargetIsa}; use crate::machinst::*; use crate::regalloc::RegisterSet; -use crate::settings::Flags; +use crate::settings::{self, Flags}; #[cfg(feature = "testing_hooks")] use crate::regalloc::RegDiversions; @@ -58,12 +58,8 @@ 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 isa_flags(&self) -> Vec { + self.backend.isa_flags() } fn hash_all_flags(&self, hasher: &mut dyn Hasher) { diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs index d1c96640dc..401863cbd8 100644 --- a/cranelift/codegen/src/machinst/mod.rs +++ b/cranelift/codegen/src/machinst/mod.rs @@ -64,7 +64,7 @@ use crate::binemit::{CodeInfo, CodeOffset, StackMap}; use crate::ir::condcodes::IntCC; use crate::ir::{Function, SourceLoc, StackSlot, Type, ValueLabel}; use crate::result::CodegenResult; -use crate::settings::Flags; +use crate::settings::{self, Flags}; use crate::value_label::ValueLabelsRanges; use alloc::boxed::Box; use alloc::vec::Vec; @@ -368,11 +368,8 @@ pub trait MachBackend { /// Return flags for this backend. fn flags(&self) -> &Flags; - /// 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; + /// Get the ISA-dependent flag values that were used to make this trait object. + fn isa_flags(&self) -> Vec; /// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher. fn hash_all_flags(&self, hasher: &mut dyn Hasher); diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index ecdb17050b..88a3c62157 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -72,6 +72,50 @@ pub struct Setting { pub values: Option<&'static [&'static str]>, } +/// Represents a setting value. +/// +/// This is used for iterating values in `Flags`. +pub struct Value { + /// The name of the setting associated with this value. + pub name: &'static str, + pub(crate) detail: detail::Detail, + pub(crate) values: Option<&'static [&'static str]>, + pub(crate) value: u8, +} + +impl Value { + /// Gets the kind of setting. + pub fn kind(&self) -> SettingKind { + match &self.detail { + detail::Detail::Enum { .. } => SettingKind::Enum, + detail::Detail::Num => SettingKind::Num, + detail::Detail::Bool { .. } => SettingKind::Bool, + detail::Detail::Preset => unreachable!(), + } + } + + /// Gets the enum value if the value is from an enum setting. + pub fn as_enum(&self) -> Option<&'static str> { + self.values.map(|v| v[self.value as usize]) + } + + /// Gets the numerical value if the value is from a num setting. + pub fn as_num(&self) -> Option { + match &self.detail { + detail::Detail::Num => Some(self.value), + _ => None, + } + } + + /// Gets the boolean value if the value is from a boolean setting. + pub fn as_bool(&self) -> Option { + match &self.detail { + detail::Detail::Bool { bit } => Some(self.value & (1 << bit) != 0), + _ => None, + } + } +} + /// Collect settings values based on a template. #[derive(Clone, Hash)] pub struct Builder { diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index de13ac0e49..36eec310ec 100644 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -11,7 +11,7 @@ pub mod ir { pub mod settings { pub use cranelift_codegen::settings::{ - builder, Builder, Configurable, Flags, OptLevel, SetError, Setting, SettingKind, + builder, Builder, Configurable, Flags, OptLevel, SetError, Setting, SettingKind, Value, }; } diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 27936ffe9d..cc28cdeaf7 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -5,10 +5,11 @@ use crate::{Engine, Module, OptLevel}; use anyhow::{anyhow, bail, Context, Result}; use bincode::Options; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; +use std::borrow::Cow; +use std::fmt; use std::str::FromStr; use std::sync::Arc; +use std::{collections::HashMap, fmt::Display}; use wasmtime_environ::Tunables; use wasmtime_environ::{isa::TargetIsa, settings}; use wasmtime_jit::{ @@ -29,7 +30,7 @@ fn bincode_options() -> impl Options { } // This exists because `wasmparser::WasmFeatures` isn't serializable -#[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] struct WasmFeatures { pub reference_types: bool, pub multi_value: bool, @@ -175,13 +176,39 @@ impl<'a> SerializedModuleData<'a> { } } +#[derive(Serialize, Deserialize, Eq, PartialEq)] +enum FlagValue { + Enum(Cow<'static, str>), + Num(u8), + Bool(bool), +} + +impl From for FlagValue { + fn from(v: settings::Value) -> Self { + match v.kind() { + settings::SettingKind::Enum => Self::Enum(v.as_enum().unwrap().into()), + settings::SettingKind::Num => Self::Num(v.as_num().unwrap()), + settings::SettingKind::Bool => Self::Bool(v.as_bool().unwrap()), + settings::SettingKind::Preset => unreachable!(), + } + } +} + +impl Display for FlagValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Enum(v) => v.fmt(f), + Self::Num(v) => v.fmt(f), + Self::Bool(v) => v.fmt(f), + } + } +} + #[derive(Serialize, Deserialize)] pub struct SerializedModule<'a> { 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, + shared_flags: HashMap, + isa_flags: HashMap, strategy: CompilationStrategy, tunables: Tunables, features: WasmFeatures, @@ -220,9 +247,16 @@ impl<'a> SerializedModule<'a> { Self { 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(), + shared_flags: isa + .flags() + .iter() + .map(|v| (v.name.to_owned(), v.into())) + .collect(), + isa_flags: isa + .isa_flags() + .into_iter() + .map(|v| (v.name.to_owned(), v.into())) + .collect(), strategy: compiler.strategy(), tunables: compiler.tunables().clone(), features: compiler.features().into(), @@ -231,19 +265,17 @@ impl<'a> SerializedModule<'a> { } } - pub fn into_module(self, engine: &Engine) -> Result { + pub fn into_module(mut self, engine: &Engine) -> Result { let compiler = engine.compiler(); let isa = compiler.isa(); self.check_triple(isa)?; + self.check_shared_flags(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() @@ -361,26 +393,63 @@ impl<'a> SerializedModule<'a> { 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); + fn check_shared_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> { + let mut shared_flags = std::mem::take(&mut self.shared_flags); + for value in isa.flags().iter() { + let name = value.name; + match shared_flags.remove(name) { + Some(v) => { + let host: FlagValue = value.into(); + if v != host { + bail!("Module was compiled with a different '{}' setting: expected '{}' but host has '{}'", name, v, host); + } + } + None => bail!("Module was compiled without setting '{}'", name), + } } - if self.flags_hash != Self::simple_hash(isa.flags()) { - bail!("Module was compiled with different Cranelift flags than the host"); + for (name, _) in shared_flags { + bail!( + "Module was compiled with setting '{}' but it is not present for the host", + name + ); } 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); + fn check_isa_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> { + let mut isa_flags = std::mem::take(&mut self.isa_flags); + for value in isa.isa_flags().into_iter() { + let name = value.name; + let host: FlagValue = value.into(); + match isa_flags.remove(name) { + Some(v) => match (&v, &host) { + (FlagValue::Bool(v), FlagValue::Bool(host)) => { + // ISA flags represent CPU features; for boolean values, only + // treat it as an error if the module was compiled with the setting enabled + // but the host does not have it enabled. + if *v && !*host { + bail!("Module was compiled with setting '{}' enabled but the host does not support it", name); + } + } + _ => { + if v != host { + bail!("Module was compiled with a different '{}' setting: expected '{}' but host has '{}'", name, v, host); + } + } + }, + None => bail!("Module was compiled without setting '{}'", name), } } + for (name, _) in isa_flags { + bail!( + "Module was compiled with setting '{}' but it is not present for the host", + name + ); + } + Ok(()) } @@ -429,12 +498,6 @@ impl<'a> SerializedModule<'a> { ); } - 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 Tunables { static_memory_bound, @@ -595,55 +658,46 @@ mod test { 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; + serialized.shared_flags.insert( + "opt_level".to_string(), + FlagValue::Enum(Cow::Borrowed("none")), + ); match serialized.into_module(&engine) { Ok(_) => unreachable!(), Err(e) => assert_eq!( e.to_string(), - "Module was compiled with different Cranelift flags than the host", + "Module was compiled with a different 'opt_level' setting: expected 'none' but host has 'speed'" ), } Ok(()) } + #[cfg(target_arch = "x86_64")] #[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()); + + serialized + .isa_flags + .insert("not_a_flag".to_string(), FlagValue::Bool(true)); match serialized.into_module(&engine) { Ok(_) => unreachable!(), - Err(e) => assert_eq!(e.to_string(), "Host is missing CPU flag 'not_a_flag'",), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with setting 'not_a_flag' but it is not present for the host", + ), } Ok(()) diff --git a/src/commands/settings.rs b/src/commands/settings.rs index 63a7beff92..f1949e8c27 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -67,11 +67,13 @@ impl SettingsCommand { println!(); println!("Settings inferred for the current host:"); - let mut enabled = isa.enabled_isa_flags(); - enabled.sort(); + let mut values = isa.isa_flags(); + values.sort_by_key(|k| k.name); - for flag in enabled { - println!(" {}", flag); + for value in values { + if value.as_bool().unwrap_or(false) { + println!(" {}", value.name); + } } }