diff --git a/Cargo.lock b/Cargo.lock index 33240d0d00d6..148263def6a7 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", @@ -3597,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 be37d8cec7a2..f909d7149f2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,12 +38,13 @@ 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" humantime = "2.0.0" wasmparser = "0.77.0" +lazy_static = "1.4.0" [dev-dependencies] env_logger = "0.8.1" @@ -90,6 +91,7 @@ vtune = ["wasmtime/vtune"] wasi-crypto = ["wasmtime-wasi-crypto"] wasi-nn = ["wasmtime-wasi-nn"] uffd = ["wasmtime/uffd"] +all-arch = ["wasmtime/all-arch"] # Try the experimental, work-in-progress new x86_64 backend. This is not stable # as of June 2020. diff --git a/RELEASES.md b/RELEASES.md index dd8ca5ddd221..59f5297a87b2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,47 @@ -------------------------------------------------------------------------------- +## Unreleased + +### Added + +* Added the `wasmtime compile` command to support AOT compilation of Wasm modules. + +* 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 `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. + +* Added CLI option `--cranelift-enable` to enable boolean settings and ISA presets. + +### 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: `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. + +* 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`. + +* Breaking: the CLI option `--enable-multi-value=false` has been changed to + `--wasm-features=-multi-value`. + +* Breaking: the CLI option `--enable-bulk-memory=false` has been changed to + `--wasm-features=-bulk-memory`. + ## 0.25.0 Released 2021-03-16. @@ -39,7 +80,7 @@ Released 2021-03-16. ### Fixed -* Interepretation of timestamps in `poll_oneoff` for WASI have been fixed to +* Interpretation of timestamps in `poll_oneoff` for WASI have been fixed to correctly use nanoseconds instead of microseconds. [#2717](https://github.com/bytecodealliance/wasmtime/pull/2717) diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 80a083250ed9..751f6316a2d1 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -53,3 +53,4 @@ wasm = ["wat", "cranelift-wasm"] experimental_x64 = ["cranelift-codegen/x64", "cranelift-filetests/experimental_x64", "cranelift-reader/experimental_x64"] experimental_arm32 = ["cranelift-codegen/arm32", "cranelift-filetests/experimental_arm32"] souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"] +all-arch = ["cranelift-codegen/all-arch"] diff --git a/cranelift/codegen/meta/src/cdsl/settings.rs b/cranelift/codegen/meta/src/cdsl/settings.rs index 217bad995529..52c51d54a82d 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 a70ddccfe18c..d7116cac9c29 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -70,6 +70,33 @@ fn gen_constructor(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatte fmtln!(fmt, "}"); } +/// Generates the `iter` function. +fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) { + fmtln!(fmt, "impl Flags {"); + fmt.indent(|fmt| { + 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, "let values = match &d.detail {"); + fmt.indent(|fmt| { + fmtln!(fmt, "detail::Detail::Preset => return None,"); + fmtln!(fmt, "detail::Detail::Enum { last, enumerators } => Some(TEMPLATE.enums(*last, *enumerators)),"); + fmtln!(fmt, "_ => None"); + }); + fmtln!(fmt, "};"); + fmtln!(fmt, "Some(Value{ name: d.name, detail: d.detail, values, value: bytes[d.offset as usize] })"); + }); + 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); @@ -136,7 +163,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, .. @@ -254,6 +281,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, .. }) => { @@ -286,6 +314,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,"); }); @@ -427,6 +456,7 @@ fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) { fmtln!(fmt, "}"); gen_constructor(group, parent, fmt); + gen_iterator(group, fmt); gen_enum_types(group, fmt); gen_getters(group, fmt); gen_descriptors(group, fmt); diff --git a/cranelift/codegen/meta/src/isa/arm64/mod.rs b/cranelift/codegen/meta/src/isa/arm64/mod.rs index cbc21347e9f7..4277e147a7d3 100644 --- a/cranelift/codegen/meta/src/isa/arm64/mod.rs +++ b/cranelift/codegen/meta/src/isa/arm64/mod.rs @@ -9,7 +9,7 @@ use crate::shared::Definitions as SharedDefinitions; fn define_settings(_shared: &SettingGroup) -> SettingGroup { 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 801e61a3d2b9..49f26391cefc 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 dddd69abb3ca..70b829787df2 100644 --- a/cranelift/codegen/meta/src/isa/x86/settings.rs +++ b/cranelift/codegen/meta/src/isa/x86/settings.rs @@ -4,37 +4,77 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup { let mut settings = SettingGroupBuilder::new("x86"); // 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, ); @@ -42,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, ); @@ -85,7 +126,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. @@ -104,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 2acc34118dd7..2233e85dbcb0 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. - - 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. - - 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. - "#, + "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. + + 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: + + - `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. - - 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. + "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. - 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/aarch64/mod.rs b/cranelift/codegen/src/isa/aarch64/mod.rs index 42b47b645edd..a6892b301dc6 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::{boxed::Box, vec::Vec}; use core::hash::{Hash, Hasher}; - use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Aarch64Architecture, Architecture, Triple}; @@ -104,6 +102,10 @@ impl MachBackend for AArch64Backend { &self.flags } + fn isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() + } + 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/aarch64/settings.rs b/cranelift/codegen/src/isa/aarch64/settings.rs index a9849c121b4e..9d3898e7b57c 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 5757b844d255..832fc46f4732 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, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Architecture, ArmArchitecture, Triple}; @@ -92,6 +92,10 @@ impl MachBackend for Arm32Backend { &self.flags } + fn isa_flags(&self) -> Vec { + Vec::new() + } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { self.flags.hash(&mut hasher); } diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs index 94895b0b6e20..079a39fa8a6d 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, vec::Vec}; use core::any::Any; use core::fmt; use core::fmt::{Debug, Formatter}; @@ -201,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 { @@ -265,8 +274,10 @@ 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 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); /// Get the default calling convention of this target. diff --git a/cranelift/codegen/src/isa/riscv/mod.rs b/cranelift/codegen/src/isa/riscv/mod.rs index 500451c72eac..2c1ebf1c85a1 100644 --- a/cranelift/codegen/src/isa/riscv/mod.rs +++ b/cranelift/codegen/src/isa/riscv/mod.rs @@ -15,8 +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; -use alloc::boxed::Box; +use alloc::{borrow::Cow, boxed::Box, vec::Vec}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -70,6 +69,10 @@ impl TargetIsa for Isa { &self.shared_flags } + fn isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() + } + 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/riscv/settings.rs b/cranelift/codegen/src/isa/riscv/settings.rs index 40aa3bed2ba0..3da9f491fd5c 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 da4065f2d0b4..c150c01f238e 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::{boxed::Box, vec::Vec}; use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse, Reg}; use target_lexicon::Triple; @@ -85,6 +85,10 @@ impl MachBackend for X64Backend { &self.flags } + fn isa_flags(&self) -> Vec { + self.x64_flags.iter().collect() + } + 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/x64/settings.rs b/cranelift/codegen/src/isa/x64/settings.rs index c5371bb1329d..501e153b46b3 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 272c3dfe5d1f..54efe7fcfdeb 100644 --- a/cranelift/codegen/src/isa/x86/mod.rs +++ b/cranelift/codegen/src/isa/x86/mod.rs @@ -21,8 +21,7 @@ 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, boxed::Box, vec::Vec}; use core::any::Any; use core::fmt; use core::hash::{Hash, Hasher}; @@ -79,6 +78,10 @@ impl TargetIsa for Isa { &self.shared_flags } + fn isa_flags(&self) -> Vec { + self.isa_flags.iter().collect() + } + 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/x86/settings.rs b/cranelift/codegen/src/isa/x86/settings.rs index 2d3a3f6698c9..f13431c1a22d 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 eb4760fae57f..543084a0b591 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; @@ -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,12 @@ impl TargetIsa for TargetIsaAdapter { self.backend.flags() } + fn isa_flags(&self) -> Vec { + self.backend.isa_flags() + } + fn hash_all_flags(&self, hasher: &mut dyn Hasher) { - self.backend.hash_all_flags(hasher) + self.backend.hash_all_flags(hasher); } fn register_info(&self) -> RegInfo { diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs index d7835a98f743..401863cbd8bf 100644 --- a/cranelift/codegen/src/machinst/mod.rs +++ b/cranelift/codegen/src/machinst/mod.rs @@ -64,18 +64,18 @@ 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; 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,10 @@ 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 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); /// Return triple for this backend. diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index 11c3639d6f5e..88a3c6215709 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -44,6 +44,78 @@ 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]>, +} + +/// 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 { @@ -66,6 +138,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 +384,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/cranelift/wasm/Cargo.toml b/cranelift/wasm/Cargo.toml index 636a7c232a1c..9c1613fa8be1 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/c-api/src/module.rs b/crates/c-api/src/module.rs index c31f125b70b3..1757191026f6 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/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index 07f2aedaeca9..36eec310ecd1 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, Setting, SettingKind, Value, + }; } pub mod isa { diff --git a/crates/environ/src/tunables.rs b/crates/environ/src/tunables.rs index 4e5aba91450e..8a786ae88b40 100644 --- a/crates/environ/src/tunables.rs +++ b/crates/environ/src/tunables.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; + /// Tunable parameters for WebAssembly compilation. -#[derive(Clone, Hash)] +#[derive(Clone, Hash, Serialize, Deserialize)] pub struct Tunables { /// For static heaps, the size in wasm pages of the heap protected by bounds checking. pub static_memory_bound: u32, diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index 30e208d9e823..12e90c3fc5ee 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/jit/Cargo.toml b/crates/jit/Cargo.toml index 0bf2f88e1a8f..b125d3a649b0 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -46,6 +46,7 @@ lightbeam = ["wasmtime-lightbeam"] jitdump = ["wasmtime-profiling/jitdump"] vtune = ["wasmtime-profiling/vtune"] parallel-compilation = ["rayon"] +all-arch = ["cranelift-codegen/all-arch"] # Try the experimental, work-in-progress new x86_64 backend. This is not stable # as of June 2020. diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 49a2d0ecd4c6..9c703f6440d0 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 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 { diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index fe94c27c02f6..6d2fc7caf27f 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -5,6 +5,7 @@ use crate::object::{build_object, ObjectUnwindInfo}; use object::write::Object; #[cfg(feature = "parallel-compilation")] use rayon::prelude::*; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; use std::mem; use wasmparser::WasmFeatures; @@ -18,7 +19,7 @@ use wasmtime_environ::{ }; /// Select which kind of compilation to use. -#[derive(Copy, Clone, Debug, Hash)] +#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq)] pub enum CompilationStrategy { /// Let Wasmtime pick the strategy. Auto, @@ -108,6 +109,11 @@ impl Compiler { self.isa.as_ref() } + /// Return the compiler's strategy. + pub fn strategy(&self) -> CompilationStrategy { + self.strategy + } + /// Return the target's frontend configuration settings. pub fn frontend_config(&self) -> TargetFrontendConfig { self.isa.frontend_config() diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index df2cd77c312a..45c57c532f6a 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -438,7 +438,7 @@ fn build_code_memory( isa: &dyn TargetIsa, obj: &[u8], module: &Module, - unwind_info: &Box<[ObjectUnwindInfo]>, + unwind_info: &[ObjectUnwindInfo], ) -> Result< ( CodeMemory, diff --git a/crates/lightbeam/Cargo.toml b/crates/lightbeam/Cargo.toml index 673494979589..de4a2e58783d 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 74e16cbd9c8f..5ff2b9676b11 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/Cargo.toml b/crates/wasmtime/Cargo.toml index b36e1afb041c..34481ee9e1ab 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -79,3 +79,6 @@ async = ["wasmtime-fiber", "wasmtime-runtime/async"] # Enables userfaultfd support in the runtime's pooling allocator when building on Linux uffd = ["wasmtime-runtime/uffd"] + +# Enables support for all architectures in JIT and the `wasmtime compile` CLI command. +all-arch = ["wasmtime-jit/all-arch"] diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 05296216f9b5..43bd809c84aa 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; @@ -399,16 +400,6 @@ 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") @@ -426,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, @@ -440,10 +426,36 @@ 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. + /// + /// Cranelift flags will not be inferred for the given target and any + /// existing target-specific Cranelift flags will be cleared. + /// + /// # 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`. @@ -884,18 +896,31 @@ impl Config { self } - /// Clears native CPU flags inferred from the host. + /// Allows setting a Cranelift boolean flag or preset. This allows + /// fine-tuning of Cranelift settings. /// - /// 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. + /// Since Cranelift flags may be unstable, this method should not be considered to be stable + /// either; other `Config` functions should be preferred for stability. /// - /// 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 + /// # 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 @@ -911,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(_) => { @@ -1419,7 +1444,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/engine.rs b/crates/wasmtime/src/engine.rs index eab429d509a1..f7d9c4eaa4f9 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -78,6 +78,43 @@ 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. + /// + /// 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> { + 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/lib.rs b/crates/wasmtime/src/lib.rs index 066d034cbcff..d843b9db43b3 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -175,6 +175,10 @@ //! lock contention is hampering multithreading throughput. This feature is only //! supported on Linux and requires a Linux kernel version 4.11 or higher. //! +//! * `all-arch` - Not enabled by default. This feature compiles in support for +//! all architectures for both the JIT compiler and the `wasmtime compile` CLI +//! command. +//! //! ## Examples //! //! In addition to the examples below be sure to check out the [online embedding diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index b068416ee764..374ec6ccd0ee 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1,10 +1,7 @@ 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::path::Path; use std::sync::Arc; use wasmparser::Validator; @@ -14,6 +11,10 @@ use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::wasm::ModuleIndex; use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; +mod serialization; + +pub use serialization::SerializedModule; + /// A compiled WebAssembly module, ready to be instantiated. /// /// A `Module` is a compiled in-memory representation of an input WebAssembly @@ -30,7 +31,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 +104,28 @@ 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 the following 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 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 /// 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 +138,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 +174,15 @@ impl Module { /// # } /// ``` pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { + let bytes = bytes.as_ref(); + + if let Some(module) = SerializedModule::from_bytes(bytes)? { + return module.into_module(engine); + } + #[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 +190,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 +228,33 @@ 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) + match Self::new( + engine, + &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` /// 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 +279,23 @@ impl Module { /// # } /// ``` pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { + if let Some(module) = SerializedModule::from_bytes(binary)? { + return module.into_module(engine); + } + + // 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 + ); + } + + // 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! { @@ -388,98 +377,12 @@ impl Module { sig } - /// Serialize compilation artifacts to the buffer. See also `deseriaize`. + /// Serialize the module to a vector of bytes. + /// + /// Use `Module::new` or `Module::from_binary` to create the module + /// from the bytes. 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)?; - 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 - /// package version. - /// - /// 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 - /// 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), - }) - } + SerializedModule::new(self).to_bytes() } /// Creates a submodule `Module` value from the specified parameters. @@ -493,7 +396,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 @@ -764,24 +667,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 compiler_fingerprint(engine: &Engine) -> u64 { - use std::hash::Hasher; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - engine.compiler().hash(&mut hasher); - hasher.finish() -} - fn _assert_send_sync() { fn _assert() {} _assert::(); diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs new file mode 100644 index 000000000000..cc28cdeaf7f3 --- /dev/null +++ b/crates/wasmtime/src/module/serialization.rs @@ -0,0 +1,813 @@ +//! Implements module serialization. + +use super::ModuleInner; +use crate::{Engine, Module, OptLevel}; +use anyhow::{anyhow, bail, Context, Result}; +use bincode::Options; +use serde::{Deserialize, Serialize}; +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::{ + 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(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 { + 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: *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, + } + } +} + +// 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, 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, + shared_flags: HashMap, + isa_flags: HashMap, + 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 { + target: isa.triple().to_string(), + 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(), + data, + tables, + } + } + + 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)?; + + 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), + }) + } + } + + 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))?; + + 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_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), + } + } + + 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(&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(()) + } + + 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 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( + static_memory_bound, + other.static_memory_bound, + "static memory bound", + )?; + Self::check_int( + static_memory_offset_guard_size, + other.static_memory_offset_guard_size, + "static memory guard size", + )?; + Self::check_int( + dynamic_memory_offset_guard_size, + other.dynamic_memory_offset_guard_size, + "dynamic memory guard size", + )?; + Self::check_bool( + generate_native_debuginfo, + other.generate_native_debuginfo, + "debug information support", + )?; + Self::check_bool( + 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( + static_memory_bound_is_maximum, + other.static_memory_bound_is_maximum, + "pooling allocation support", + )?; + + 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( + reference_types, + other.reference_types, + "WebAssembly reference types support", + )?; + Self::check_bool( + multi_value, + other.multi_value, + "WebAssembly multi-value support", + )?; + Self::check_bool( + bulk_memory, + other.bulk_memory, + "WebAssembly bulk memory support", + )?; + Self::check_bool( + module_linking, + other.module_linking, + "WebAssembly module linking 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( + deterministic_only, + other.deterministic_only, + "WebAssembly deterministic-only support", + )?; + Self::check_bool( + multi_memory, + other.multi_memory, + "WebAssembly multi-memory support", + )?; + Self::check_bool( + exceptions, + other.exceptions, + "WebAssembly exceptions support", + )?; + Self::check_bool( + memory64, + other.memory64, + "WebAssembly 64-bit memory support", + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Config; + + #[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_cranelift_flags_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + 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 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 + .insert("not_a_flag".to_string(), FlagValue::Bool(true)); + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with setting 'not_a_flag' but it is not present for the host", + ), + } + + Ok(()) + } + + #[cfg(feature = "lightbeam")] + #[test] + fn test_compilation_strategy_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.strategy = CompilationStrategy::Lightbeam; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with strategy 'Cranelift'", + ), + } + + Ok(()) + } + + #[test] + fn test_tunables_int_mismatch() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.static_memory_offset_guard_size = 0; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"), + } + + Ok(()) + } + + #[test] + fn test_tunables_bool_mismatch() -> Result<()> { + let mut config = Config::new(); + config.interruptable(true); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.interruptable = false; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled without interruption support but it is enabled for the host" + ), + } + + let mut config = Config::new(); + config.interruptable(false); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.tunables.interruptable = true; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with interruption support but it is not enabled for the host" + ), + } + + Ok(()) + } + + #[test] + fn test_feature_mismatch() -> Result<()> { + let mut config = Config::new(); + config.wasm_simd(true); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.features.simd = false; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly SIMD support but it is enabled for the host"), + } + + let mut config = Config::new(); + config.wasm_simd(false); + + let engine = Engine::new(&config)?; + let module = Module::new(&engine, "(module)")?; + + let mut serialized = SerializedModule::new(&module); + serialized.features.simd = true; + + match serialized.into_module(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly SIMD support but it is not enabled for the host"), + } + + Ok(()) + } +} diff --git a/docs/cli-options.md b/docs/cli-options.md index d1204314ab7c..be78272a7811 100644 --- a/docs/cli-options.md +++ b/docs/cli-options.md @@ -80,3 +80,29 @@ 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. + +## `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/examples/serialize.rs b/examples/serialize.rs index dd30b47a98a1..3cd709e7489d 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/bin/wasmtime.rs b/src/bin/wasmtime.rs index 89c1078f4816..475f2c607bd9 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, SettingsCommand, WasmToObjCommand, WastCommand, }; /// Wasmtime WebAssembly Runtime @@ -38,10 +38,14 @@ 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), + /// Displays available Cranelift settings for a target. + Settings(SettingsCommand), /// 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), @@ -49,10 +53,12 @@ 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::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 e9891bab99a9..ebed3d732bdd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,8 +1,10 @@ //! The module for the Wasmtime CLI commands. +mod compile; mod config; mod run; +mod settings; mod wasm2obj; mod wast; -pub use self::{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 new file mode 100644 index 000000000000..7b9a7ebcc20e --- /dev/null +++ b/src/commands/compile.rs @@ -0,0 +1,273 @@ +//! The module that implements the `wasmtime compile` command. + +use crate::CommonOptions; +use anyhow::{bail, Context, Result}; +use std::fs; +use std::path::PathBuf; +use structopt::{clap::AppSettings, StructOpt}; +use target_lexicon::Triple; +use wasmtime::Engine; + +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 --cranelift-enable skylake foo.wasm\n", + crate::WASM_FEATURES.as_str() + ) + }; +} + +/// Compiles a WebAssembly module. +#[derive(StructOpt)] +#[structopt( + name = "compile", + version = env!("CARGO_PKG_VERSION"), + setting = AppSettings::ColoredHelp, + after_help = AFTER_HELP.as_str() +)] +pub struct CompileCommand { + #[structopt(flatten)] + common: CommonOptions, + + /// Enable support for interrupting WebAssembly code. + #[structopt(long)] + interruptable: 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<()> { + self.common.init_logging(); + + let target = self + .target + .take() + .unwrap_or_else(|| Triple::host().to_string()); + + let mut config = self.common.config(Some(&target))?; + config.interruptable(self.interruptable); + + 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 + }); + + fs::write(output, engine.precompile_module(&input)?)?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + use tempfile::NamedTempFile; + use wasmtime::{Instance, Module, 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", + "--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(), + ])?; + + 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", + "--cranelift-enable", + "has_lse", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + #[test] + 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(); + + // aarch64 flags should not be supported + let command = CompileCommand::from_iter_safe(vec![ + "compile", + "--disable-logging", + "--cranelift-enable", + "has_lse", + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + assert_eq!( + command.execute().unwrap_err().to_string(), + "No existing setting named 'has_lse'" + ); + + 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", + "--cranelift-enable", + preset, + "-o", + output_path.to_str().unwrap(), + input_path.to_str().unwrap(), + ])?; + + command.execute()?; + } + + Ok(()) + } +} diff --git a/src/commands/config.rs b/src/commands/config.rs index 5cafac4e77e7..a434b864368d 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -17,7 +17,7 @@ pub enum ConfigCommand { impl ConfigCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { + pub fn execute(self) -> Result<()> { match self { Self::New(c) => c.execute(), } @@ -35,7 +35,7 @@ pub struct ConfigNewCommand { impl ConfigNewCommand { /// Executes the command. - pub fn execute(&self) -> Result<()> { + pub fn execute(self) -> Result<()> { let path = wasmtime_cache::create_new_config(self.path.as_ref())?; println!( diff --git a/src/commands/run.rs b/src/commands/run.rs index abc54bf9b1d7..9c57add91fee 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; @@ -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()), } } @@ -69,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, @@ -96,7 +101,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 +132,9 @@ 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(); - } + self.common.init_logging(); - 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/settings.rs b/src/commands/settings.rs new file mode 100644 index 000000000000..f1949e8c27f8 --- /dev/null +++ b/src/commands/settings.rs @@ -0,0 +1,98 @@ +//! 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, "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 { + 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()); + + for (collection, max, header) in &mut [enums, nums, bools, presets] { + if collection.is_empty() { + continue; + } + + collection.sort_by_key(|k| k.name); + println!(); + Self::print_settings(header, collection, *max); + } + + if self.target.is_none() { + let isa = settings.finish(settings::Flags::new(settings::builder())); + println!(); + println!("Settings inferred for the current host:"); + + let mut values = isa.isa_flags(); + values.sort_by_key(|k| k.name); + + for value in values { + if value.as_bool().unwrap_or(false) { + println!(" {}", value.name); + } + } + } + + Ok(()) + } + + fn print_settings(header: &str, settings: &[Setting], 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/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index 4688f985dcea..ab914d8edc1b 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -1,23 +1,26 @@ //! 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::{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; -/// 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."; - -fn parse_target(s: &str) -> Result { - Triple::from_str(&s).map_err(|e| anyhow!(e)) +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 @@ -26,7 +29,7 @@ fn parse_target(s: &str) -> Result { 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)] @@ -47,17 +50,8 @@ 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<()> { + 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 52dbff3aff3c..cd749e61a640 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -1,18 +1,25 @@ //! 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}; 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)] @@ -25,15 +32,10 @@ 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<()> { + self.common.init_logging(); - let config = self.common.config()?; + let config = self.common.config(None)?; let store = Store::new(&Engine::new(&config)?); let mut wast_context = WastContext::new(store); diff --git a/src/lib.rs b/src/lib.rs index 39bda21f668d..3485b6288975 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,12 +23,55 @@ ) )] +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; use wasmtime::{Config, ProfilingStrategy, Strategy}; pub use obj::compile_to_obj; @@ -91,6 +134,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, @@ -103,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, - /// Enable support for reference types - #[structopt(long)] - enable_reference_types: Option, + /// Enable support for reference types (deprecated; use `--wasm-features=reference-types`) + #[structopt(long, hidden = true)] + enable_reference_types: bool, - /// Enable support for multi-value functions - #[structopt(long)] - enable_multi_value: Option, + /// 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, - /// Enable support for bulk memory instructions - #[structopt(long)] - enable_bulk_memory: Option, + /// 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, @@ -151,30 +202,42 @@ 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 + /// 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), - default_value = "2", + verbatim_doc_comment, )] - 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_flags: Vec, + /// 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)>, + + /// 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 /// 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,31 +250,48 @@ struct CommonOptions { } impl CommonOptions { - fn config(&self) -> Result { + 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 { + 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) .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_reference_types( - self.enable_reference_types - .unwrap_or(cfg!(target_arch = "x86_64")) - || self.enable_all, - ) - .wasm_multi_value(self.enable_multi_value.unwrap_or(true) || 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); - for CraneliftFlag { name, value } in &self.cranelift_flags { + + self.enable_wasm_features(&mut config); + + for name in &self.cranelift_enable { + unsafe { + config.cranelift_flag_enable(name)?; + } + } + + for (name, value) in &self.cranelift_set { unsafe { - config.cranelift_other_flag(name, value)?; + config.cranelift_flag_set(name, value)?; } } + if !self.disable_cache { match &self.config { Some(path) => { @@ -222,22 +302,43 @@ 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, - (false, other) => other, + (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed), } } } @@ -255,12 +356,59 @@ fn parse_opt_level(opt_level: &str) -> Result { } } -struct CraneliftFlag { - name: String, - value: String, -} +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(); -fn parse_cranelift_flag(name_and_value: &str) -> Result { + 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, + }) +} +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() @@ -272,5 +420,158 @@ 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 { + 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"); } diff --git a/tests/all/module.rs b/tests/all/module.rs index 18b2f72c58c7..279c91e829f4 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -1,57 +1,78 @@ +use anyhow::Result; 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 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::new(&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(), + 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( - &Engine::new( - &Config::new() - .cranelift_clear_cpu_flags() - .cranelift_opt_level(OptLevel::None), - ) - .unwrap(), + let res = Module::new( + &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() - }) + let res = Module::new( + &Engine::new( + Config::new() + .target(&target_lexicon::Triple::host().to_string()) + .unwrap(), + ) .unwrap(), &bytes, ); assert!(res.is_err()); } } + +#[test] +fn aot_compiles() -> Result<()> { + let engine = Engine::default(); + let bytes = engine.precompile_module( + "(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(), + )?; + + 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(()) +} diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs index b010573c6e7d..101655eff318 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 a77f62b077a2..abb0f7752d43 100644 --- a/tests/all/module_serialize.rs +++ b/tests/all/module_serialize.rs @@ -7,10 +7,27 @@ 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, &[])?) } +#[test] +fn test_version_mismatch() -> Result<()> { + let engine = Engine::default(); + let mut buffer = serialize(&engine, "(module)")?; + buffer[13 /* header length */ + 1 /* version length */] = 'x' as u8; + + match Module::new(&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(