From 503129ad9103028cd0b56ae969848ec1bbf25bc7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Jan 2021 15:59:12 -0600 Subject: [PATCH] Add a method to share `Config` across machines (#2608) With `Module::{serialize,deserialize}` it should be possible to share wasmtime modules across machines or CPUs. Serialization, however, embeds a hash of all configuration values, including cranelift compilation settings. By default wasmtime's selection of the native ISA would enable ISA flags according to CPU features available on the host, but the same CPU features may not be available across two machines. This commit adds a `Config::cranelift_clear_cpu_flags` method which allows clearing the target-specific ISA flags that are automatically inferred by default for the native CPU. Options can then be incrementally built back up as-desired with teh `cranelift_other_flag` method. --- cranelift/codegen/meta/src/gen_settings.rs | 2 +- cranelift/codegen/src/isa/aarch64/mod.rs | 5 ++ cranelift/codegen/src/isa/arm32/mod.rs | 5 ++ cranelift/codegen/src/isa/mod.rs | 5 ++ cranelift/codegen/src/isa/riscv/mod.rs | 6 +++ cranelift/codegen/src/isa/x64/mod.rs | 6 +++ cranelift/codegen/src/isa/x86/mod.rs | 6 +++ cranelift/codegen/src/machinst/adapter.rs | 5 ++ cranelift/codegen/src/machinst/mod.rs | 5 ++ cranelift/codegen/src/settings.rs | 2 +- cranelift/native/src/lib.rs | 7 +-- crates/jit/src/compiler.rs | 8 +--- crates/jit/src/native.rs | 5 ++ crates/wasmtime/src/config.rs | 14 ++++++ tests/all/main.rs | 1 + tests/all/module.rs | 54 ++++++++++++++++++++++ 16 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 tests/all/module.rs diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index 2ed5941b80..a70ddccfe1 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -418,7 +418,7 @@ fn gen_display(group: &SettingGroup, fmt: &mut Formatter) { fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) { // Generate struct. - fmtln!(fmt, "#[derive(Clone)]"); + fmtln!(fmt, "#[derive(Clone, Hash)]"); fmt.doc_comment(format!("Flags group `{}`.", group.name)); fmtln!(fmt, "pub struct Flags {"); fmt.indent(|fmt| { diff --git a/cranelift/codegen/src/isa/aarch64/mod.rs b/cranelift/codegen/src/isa/aarch64/mod.rs index 11eb0a6ea6..af13cb70c0 100644 --- a/cranelift/codegen/src/isa/aarch64/mod.rs +++ b/cranelift/codegen/src/isa/aarch64/mod.rs @@ -8,6 +8,7 @@ use crate::result::CodegenResult; use crate::settings; use alloc::boxed::Box; +use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Aarch64Architecture, Architecture, Triple}; @@ -95,6 +96,10 @@ impl MachBackend for AArch64Backend { &self.flags } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { + self.flags.hash(&mut hasher); + } + fn reg_universe(&self) -> &RealRegUniverse { &self.reg_universe } diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index 6ab0f9c57c..3976d74ba6 100644 --- a/cranelift/codegen/src/isa/arm32/mod.rs +++ b/cranelift/codegen/src/isa/arm32/mod.rs @@ -8,6 +8,7 @@ use crate::result::CodegenResult; use crate::settings; use alloc::boxed::Box; +use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse}; use target_lexicon::{Architecture, ArmArchitecture, Triple}; @@ -90,6 +91,10 @@ impl MachBackend for Arm32Backend { &self.flags } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { + self.flags.hash(&mut hasher); + } + fn reg_universe(&self) -> &RealRegUniverse { &self.reg_universe } diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs index 73a83dda34..bfc4e0d0d0 100644 --- a/cranelift/codegen/src/isa/mod.rs +++ b/cranelift/codegen/src/isa/mod.rs @@ -69,6 +69,7 @@ use alloc::boxed::Box; use core::any::Any; use core::fmt; use core::fmt::{Debug, Formatter}; +use core::hash::Hasher; use target_lexicon::{triple, Architecture, PointerWidth, Triple}; use thiserror::Error; @@ -265,6 +266,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. + fn hash_all_flags(&self, hasher: &mut dyn Hasher); + /// Get the default calling convention of this target. fn default_call_conv(&self) -> CallConv { CallConv::triple_default(self.triple()) diff --git a/cranelift/codegen/src/isa/riscv/mod.rs b/cranelift/codegen/src/isa/riscv/mod.rs index e69a3a0e12..500451c72e 100644 --- a/cranelift/codegen/src/isa/riscv/mod.rs +++ b/cranelift/codegen/src/isa/riscv/mod.rs @@ -19,6 +19,7 @@ use alloc::borrow::Cow; use alloc::boxed::Box; use core::any::Any; use core::fmt; +use core::hash::{Hash, Hasher}; use target_lexicon::{PointerWidth, Triple}; #[allow(dead_code)] @@ -69,6 +70,11 @@ impl TargetIsa for Isa { &self.shared_flags } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { + self.shared_flags.hash(&mut hasher); + self.isa_flags.hash(&mut hasher); + } + fn register_info(&self) -> RegInfo { registers::INFO.clone() } diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs index ca809e2d55..28cd503615 100644 --- a/cranelift/codegen/src/isa/x64/mod.rs +++ b/cranelift/codegen/src/isa/x64/mod.rs @@ -11,6 +11,7 @@ use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, use crate::result::CodegenResult; use crate::settings::{self as shared_settings, Flags}; use alloc::boxed::Box; +use core::hash::{Hash, Hasher}; use regalloc::{PrettyPrint, RealRegUniverse, Reg}; use target_lexicon::Triple; @@ -82,6 +83,11 @@ impl MachBackend for X64Backend { &self.flags } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { + self.flags.hash(&mut hasher); + self.x64_flags.hash(&mut hasher); + } + fn name(&self) -> &'static str { "x64" } diff --git a/cranelift/codegen/src/isa/x86/mod.rs b/cranelift/codegen/src/isa/x86/mod.rs index cbdeb3069d..272c3dfe5d 100644 --- a/cranelift/codegen/src/isa/x86/mod.rs +++ b/cranelift/codegen/src/isa/x86/mod.rs @@ -25,6 +25,7 @@ use alloc::borrow::Cow; use alloc::boxed::Box; use core::any::Any; use core::fmt; +use core::hash::{Hash, Hasher}; use target_lexicon::{PointerWidth, Triple}; #[allow(dead_code)] @@ -78,6 +79,11 @@ impl TargetIsa for Isa { &self.shared_flags } + fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) { + self.shared_flags.hash(&mut hasher); + self.isa_flags.hash(&mut hasher); + } + fn uses_cpu_flags(&self) -> bool { true } diff --git a/cranelift/codegen/src/machinst/adapter.rs b/cranelift/codegen/src/machinst/adapter.rs index 4b3be0f3c0..eb4760fae5 100644 --- a/cranelift/codegen/src/machinst/adapter.rs +++ b/cranelift/codegen/src/machinst/adapter.rs @@ -14,6 +14,7 @@ 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; @@ -58,6 +59,10 @@ impl TargetIsa for TargetIsaAdapter { self.backend.flags() } + fn hash_all_flags(&self, hasher: &mut dyn Hasher) { + self.backend.hash_all_flags(hasher) + } + fn register_info(&self) -> RegInfo { // Called from function's Display impl, so we need a stub here. RegInfo { diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs index 7ed2661dda..297d531955 100644 --- a/cranelift/codegen/src/machinst/mod.rs +++ b/cranelift/codegen/src/machinst/mod.rs @@ -76,6 +76,7 @@ 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; @@ -373,6 +374,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. + fn hash_all_flags(&self, hasher: &mut dyn Hasher); + /// Return triple for this backend. fn triple(&self) -> Triple; diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index 6f25b134af..a1bc954c54 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -188,7 +188,7 @@ pub type SetResult = Result; /// The settings objects themselves are generated and appear in the `isa/*/settings.rs` modules. /// Each settings object provides a `predicate_view()` method that makes it possible to query /// ISA predicates by number. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Hash)] pub struct PredicateView<'a>(&'a [u8]); impl<'a> PredicateView<'a> { diff --git a/cranelift/native/src/lib.rs b/cranelift/native/src/lib.rs index 3bd5716dac..8dffd8ff79 100644 --- a/cranelift/native/src/lib.rs +++ b/cranelift/native/src/lib.rs @@ -34,7 +34,7 @@ use raw_cpuid::CpuId; /// machine, or `Err(())` if the host machine is not supported /// in the current configuration. pub fn builder() -> Result { - builder_with_backend_variant(isa::BackendVariant::Any) + builder_with_options(isa::BackendVariant::Any, true) } /// Return an `isa` builder configured for the current host @@ -44,8 +44,9 @@ pub fn builder() -> Result { /// Selects the given backend variant specifically; this is /// useful when more than oen backend exists for a given target /// (e.g., on x86-64). -pub fn builder_with_backend_variant( +pub fn builder_with_options( variant: isa::BackendVariant, + infer_native_flags: bool, ) -> Result { let mut isa_builder = isa::lookup_variant(Triple::host(), variant).map_err(|err| match err { @@ -55,7 +56,7 @@ pub fn builder_with_backend_variant( isa::LookupError::Unsupported => "unsupported architecture", })?; - if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + if infer_native_flags && cfg!(any(target_arch = "x86", target_arch = "x86_64")) { parse_x86_cpuid(&mut isa_builder)?; } diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 5adb0e8312..fe94c27c02 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -182,14 +182,10 @@ impl Hash for Compiler { // misc tunables. strategy.hash(hasher); isa.triple().hash(hasher); - features.hash(hasher); - // TODO: if this `to_string()` is too expensive then we should upstream - // a native hashing ability of flags into cranelift itself, but - // compilation and/or cache loading is relatively expensive so seems - // unlikely. - isa.flags().to_string().hash(hasher); + isa.hash_all_flags(hasher); isa.frontend_config().hash(hasher); tunables.hash(hasher); + features.hash(hasher); // Catch accidental bugs of reusing across crate versions. env!("CARGO_PKG_VERSION").hash(hasher); diff --git a/crates/jit/src/native.rs b/crates/jit/src/native.rs index 9d1fdd7b66..afcf83d3cc 100644 --- a/crates/jit/src/native.rs +++ b/crates/jit/src/native.rs @@ -6,6 +6,11 @@ pub fn builder() -> cranelift_codegen::isa::Builder { cranelift_native::builder().expect("host machine is not a supported target") } +pub fn builder_without_flags() -> cranelift_codegen::isa::Builder { + cranelift_native::builder_with_options(cranelift_codegen::isa::BackendVariant::Any, false) + .expect("host machine is not a supported target") +} + pub fn call_conv() -> cranelift_codegen::isa::CallConv { use target_lexicon::HOST; cranelift_codegen::isa::CallConv::triple_default(&HOST) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index e6e3f68952..80b10548ed 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -385,6 +385,20 @@ impl Config { self } + /// Clears native CPU flags inferred from the host. + /// + /// By default Wasmtime will tune generated code for the host that Wasmtime + /// itself is running on. If you're compiling on one host, however, and + /// shipping artifacts to another host then this behavior may not be + /// desired. This function will clear all inferred native CPU features. + /// + /// To enable CPU features afterwards it's recommended to use the + /// [`Config::cranelift_other_flag`] method. + pub fn cranelift_clear_cpu_flags(&mut self) -> &mut Self { + self.isa_flags = native::builder_without_flags(); + self + } + /// Allows settings another Cranelift flag defined by a flag name and value. This allows /// fine-tuning of Cranelift settings. /// diff --git a/tests/all/main.rs b/tests/all/main.rs index 414708144f..1f6cb80459 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -12,6 +12,7 @@ mod instance; mod invoke_func_via_table; mod linker; mod memory_creator; +mod module; mod module_linking; mod module_serialize; mod name; diff --git a/tests/all/module.rs b/tests/all/module.rs new file mode 100644 index 0000000000..64384e0de6 --- /dev/null +++ b/tests/all/module.rs @@ -0,0 +1,54 @@ +use wasmtime::*; + +#[test] +fn caches_across_engines() { + let mut c = Config::new(); + c.cranelift_clear_cpu_flags(); + + let bytes = Module::new(&Engine::new(&c), "(module)") + .unwrap() + .serialize() + .unwrap(); + + let res = Module::deserialize( + &Engine::new(&Config::new().cranelift_clear_cpu_flags()), + &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), + ), + &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), + ), + &bytes, + ); + assert!(res.is_err()); + + // differ in cpu-specific 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() + }), + &bytes, + ); + assert!(res.is_err()); + } +}