diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 66eb8ee44e..2f024d8e16 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -1,13 +1,17 @@ //! Memory management for executable code. use crate::unwind::UnwindRegistration; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use object::read::{File, Object, ObjectSection}; use std::ffi::c_void; use std::mem::ManuallyDrop; use wasmtime_jit_icache_coherence as icache_coherence; use wasmtime_runtime::MmapVec; +/// Name of the section in ELF files indicating that branch protection was +/// enabled for the compiled code. +pub const ELF_WASM_BTI: &str = ".wasmtime.bti"; + /// Management of executable memory within a `MmapVec` /// /// This type consumes ownership of a region of memory and will manage the @@ -80,7 +84,7 @@ impl CodeMemory { /// After this function executes all JIT code should be ready to execute. /// The various parsed results of the internals of the `MmapVec` are /// returned through the `Publish` structure. - pub fn publish(&mut self, enable_branch_protection: bool) -> Result> { + pub fn publish(&mut self) -> Result> { assert!(!self.published); self.published = true; @@ -92,7 +96,10 @@ impl CodeMemory { }; let mmap_ptr = self.mmap.as_ptr() as u64; - // Sanity-check that all sections are aligned correctly. + // Sanity-check that all sections are aligned correctly and + // additionally probe for a few sections that we're interested in. + let mut enable_branch_protection = None; + let mut text = None; for section in ret.obj.sections() { let data = match section.data() { Ok(data) => data, @@ -108,17 +115,25 @@ impl CodeMemory { section.align() ); } - } - // Find the `.text` section with executable code in it. - let text = match ret.obj.section_by_name(".text") { - Some(section) => section, + match section.name().unwrap_or("") { + ELF_WASM_BTI => match data.len() { + 1 => enable_branch_protection = Some(data[0] != 0), + _ => bail!("invalid `{ELF_WASM_BTI}` section"), + }, + ".text" => { + ret.text = data; + text = Some(section); + } + _ => {} + } + } + let enable_branch_protection = + enable_branch_protection.ok_or_else(|| anyhow!("missing `{ELF_WASM_BTI}` section"))?; + let text = match text { + Some(text) => text, None => return Ok(ret), }; - ret.text = match text.data() { - Ok(data) if !data.is_empty() => data, - _ => return Ok(ret), - }; // The unsafety here comes from a few things: // diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index e7ffbadae3..4498157c15 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -136,9 +136,6 @@ struct Metadata { /// Note that even if this flag is `true` sections may be missing if they /// weren't found in the original wasm module itself. has_wasm_debuginfo: bool, - - /// Whether or not branch protection is enabled. - is_branch_protection_enabled: bool, } /// Finishes compilation of the `translation` specified, producing the final @@ -163,7 +160,6 @@ pub fn finish_compile( funcs: PrimaryMap, trampolines: Vec, tunables: &Tunables, - is_branch_protection_enabled: bool, ) -> Result<(MmapVec, CompiledModuleInfo)> { let ModuleTranslation { mut module, @@ -269,7 +265,6 @@ pub fn finish_compile( has_unparsed_debuginfo, code_section_offset: debuginfo.wasm_file.code_section_offset, has_wasm_debuginfo: tunables.parse_wasm_debuginfo, - is_branch_protection_enabled, }, }; bincode::serialize_into(&mut bytes, &info)?; @@ -500,7 +495,7 @@ impl CompiledModule { dwarf_sections, }; ret.code_memory - .publish(ret.meta.is_branch_protection_enabled) + .publish() .context("failed to publish code memory")?; ret.register_debug_and_profiling(profiler)?; diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 9f80b00856..8ed600382b 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -27,7 +27,7 @@ mod instantiate; mod profiling; mod unwind; -pub use crate::code_memory::CodeMemory; +pub use crate::code_memory::{CodeMemory, ELF_WASM_BTI}; pub use crate::instantiate::{ finish_compile, mmap_vec_from_obj, subslice_range, CompiledModule, CompiledModuleInfo, SetupError, SymbolizeContext, diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index bac0eb4f7e..aea26c6d95 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -152,7 +152,7 @@ impl Component { // do so. This should build up a mapping from // `SignatureIndex` to `VMSharedSignatureIndex` once and // then reuse that for each module somehow. - Module::from_parts(engine, mmap, info, types.clone()) + Module::from_parts(engine, mmap, Some((info, types.clone().into()))) })?; Ok(modules.into_iter().collect::>()) @@ -164,7 +164,7 @@ impl Component { let static_modules = static_modules?; let (lowerings, always_trap, transcoders, trampolines, trampoline_obj) = trampolines?; let mut trampoline_obj = CodeMemory::new(trampoline_obj); - let code = trampoline_obj.publish(engine.compiler().is_branch_protection_enabled())?; + let code = trampoline_obj.publish()?; let text = wasmtime_jit::subslice_range(code.text, code.mmap); // This map is used to register all known tramplines in the @@ -266,6 +266,7 @@ impl Component { trampolines?, &mut obj, )?; + engine.append_bti(&mut obj); return Ok(( lower, traps, diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 3d36dd849a..fff01ebaaf 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -1,16 +1,21 @@ use crate::signatures::SignatureRegistry; use crate::Config; -use anyhow::Result; +use anyhow::{Context, Result}; +use object::write::{Object, StandardSegment}; +use object::SectionKind; use once_cell::sync::OnceCell; #[cfg(feature = "parallel-compilation")] use rayon::prelude::*; +use std::path::Path; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; #[cfg(feature = "cache")] use wasmtime_cache::CacheConfig; use wasmtime_environ::FlagValue; use wasmtime_jit::ProfilingAgent; -use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator}; +use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator, MmapVec}; + +mod serialization; /// An `Engine` which is a global context for compilation and management of wasm /// modules. @@ -216,9 +221,8 @@ impl Engine { pub fn precompile_module(&self, bytes: &[u8]) -> Result> { #[cfg(feature = "wat")] let bytes = wat::parse_bytes(&bytes)?; - let (mmap, _, types) = crate::Module::build_artifacts(self, &bytes)?; - crate::module::SerializedModule::from_artifacts(self, &mmap, &types) - .to_bytes(&self.config().module_version) + let (mmap, _) = crate::Module::build_artifacts(self, &bytes)?; + Ok(mmap.to_vec()) } pub(crate) fn run_maybe_parallel< @@ -292,6 +296,7 @@ impl Engine { .clone() .map_err(anyhow::Error::msg) } + fn _check_compatible_with_native_host(&self) -> Result<(), String> { #[cfg(compiler)] { @@ -546,6 +551,43 @@ impl Engine { flag )) } + + #[cfg(compiler)] + pub(crate) fn append_compiler_info(&self, obj: &mut Object<'_>) { + serialization::append_compiler_info(self, obj); + } + + #[cfg(compiler)] + pub(crate) fn append_bti(&self, obj: &mut Object<'_>) { + let section = obj.add_section( + obj.segment_name(StandardSegment::Data).to_vec(), + wasmtime_jit::ELF_WASM_BTI.as_bytes().to_vec(), + SectionKind::ReadOnlyData, + ); + let contents = if self.compiler().is_branch_protection_enabled() { + 1 + } else { + 0 + }; + obj.append_section_data(section, &[contents], 1); + } + + pub(crate) fn load_mmap_bytes(&self, bytes: &[u8]) -> Result { + self.load_mmap(MmapVec::from_slice(bytes)?) + } + + pub(crate) fn load_mmap_file(&self, path: &Path) -> Result { + self.load_mmap( + MmapVec::from_file(path).with_context(|| { + format!("failed to create file mapping for: {}", path.display()) + })?, + ) + } + + fn load_mmap(&self, mmap: MmapVec) -> Result { + serialization::check_compatible(self, &mmap)?; + Ok(mmap) + } } impl Default for Engine { diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs new file mode 100644 index 0000000000..304a9797a8 --- /dev/null +++ b/crates/wasmtime/src/engine/serialization.rs @@ -0,0 +1,590 @@ +//! This module implements serialization and deserialization of `Engine` +//! configuration data which is embedded into compiled artifacts of Wasmtime. +//! +//! The data serialized here is used to double-check that when a module is +//! loaded from one host onto another that it's compatible with the target host. +//! Additionally though this data is the first data read from a precompiled +//! artifact so it's "extra hardened" to provide reasonable-ish error messages +//! for mismatching wasmtime versions. Once something successfully deserializes +//! here it's assumed it's meant for this wasmtime so error messages are in +//! general much worse afterwards. +//! +//! Wasmtime AOT artifacts are ELF files so the data for the engine here is +//! stored into a section of the output file. The structure of this section is: +//! +//! 1. A version byte, currently `VERSION`. +//! 2. A byte indicating how long the next field is. +//! 3. A version string of the length of the previous byte value. +//! 4. A `bincode`-encoded `Metadata` structure. +//! +//! This is hoped to help distinguish easily Wasmtime-based ELF files from +//! other random ELF files, as well as provide better error messages for +//! using wasmtime artifacts across versions. + +use crate::{Engine, ModuleVersionStrategy}; +use anyhow::{anyhow, bail, Context, Result}; +use object::write::{Object, StandardSegment}; +use object::{File, Object as _, ObjectSection, SectionKind}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::str::FromStr; +use wasmtime_environ::{FlagValue, Tunables}; +use wasmtime_runtime::MmapVec; + +const VERSION: u8 = 0; +const ELF_WASM_ENGINE: &str = ".wasmtime.engine"; + +/// Produces a blob of bytes by serializing the `engine`'s configuration data to +/// be checked, perhaps in a different process, with the `check_compatible` +/// method below. +/// +/// The blob of bytes is inserted into the object file specified to become part +/// of the final compiled artifact. +#[cfg(compiler)] +pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>) { + let section = obj.add_section( + obj.segment_name(StandardSegment::Data).to_vec(), + ELF_WASM_ENGINE.as_bytes().to_vec(), + SectionKind::ReadOnlyData, + ); + let mut data = Vec::new(); + data.push(VERSION); + let version = match &engine.config().module_version { + ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"), + ModuleVersionStrategy::Custom(c) => c, + ModuleVersionStrategy::None => "", + }; + // This precondition is checked in Config::module_version: + assert!( + version.len() < 256, + "package version must be less than 256 bytes" + ); + data.push(version.len() as u8); + data.extend_from_slice(version.as_bytes()); + bincode::serialize_into(&mut data, &Metadata::new(engine)).unwrap(); + obj.set_section_data(section, data, 1); +} + +/// Verifies that the serialized engine in `mmap` is compatible with the +/// `engine` provided. +/// +/// This function will verify that the `mmap` provided can be deserialized +/// successfully and that the contents are all compatible with the `engine` +/// provided here, notably compatible wasm features are enabled, compatible +/// compiler options, etc. If a mismatch is found and the compilation metadata +/// specified is incompatible then an error is returned. +pub fn check_compatible(engine: &Engine, mmap: &MmapVec) -> Result<()> { + // Ideally we'd only `File::parse` once and avoid the linear + // `section_by_name` search here but the general serialization code isn't + // structured well enough to make this easy and additionally it's not really + // a perf issue right now so doing that is left for another day's + // refactoring. + let obj = File::parse(&mmap[..]).context("failed to parse precompiled artifact as an ELF")?; + let data = obj + .section_by_name(ELF_WASM_ENGINE) + .ok_or_else(|| anyhow!("failed to find section `{ELF_WASM_ENGINE}`"))? + .data()?; + let (first, data) = data + .split_first() + .ok_or_else(|| anyhow!("invalid engine section"))?; + if *first != VERSION { + bail!("mismatched version in engine section"); + } + let (len, data) = data + .split_first() + .ok_or_else(|| anyhow!("invalid engine section"))?; + let len = usize::from(*len); + let (version, data) = if data.len() < len + 1 { + bail!("engine section too small") + } else { + data.split_at(len) + }; + + match &engine.config().module_version { + ModuleVersionStrategy::WasmtimeVersion => { + let version = std::str::from_utf8(version)?; + if version != env!("CARGO_PKG_VERSION") { + bail!( + "Module was compiled with incompatible Wasmtime version '{}'", + version + ); + } + } + ModuleVersionStrategy::Custom(v) => { + let version = std::str::from_utf8(&version)?; + if version != v { + bail!( + "Module was compiled with incompatible version '{}'", + version + ); + } + } + ModuleVersionStrategy::None => { /* ignore the version info, accept all */ } + } + bincode::deserialize::(data)?.check_compatible(engine) +} + +#[derive(Serialize, Deserialize)] +struct Metadata { + target: String, + shared_flags: BTreeMap, + isa_flags: BTreeMap, + tunables: Tunables, + features: WasmFeatures, +} + +// This exists because `wasmparser::WasmFeatures` isn't serializable +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +struct WasmFeatures { + reference_types: bool, + multi_value: bool, + bulk_memory: bool, + component_model: bool, + simd: bool, + threads: bool, + tail_call: bool, + deterministic_only: bool, + multi_memory: bool, + exceptions: bool, + memory64: bool, + relaxed_simd: bool, + extended_const: bool, +} + +impl Metadata { + #[cfg(compiler)] + fn new(engine: &Engine) -> Metadata { + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + component_model, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + relaxed_simd, + extended_const, + + // Always on; we don't currently have knobs for these. + mutable_global: _, + saturating_float_to_int: _, + sign_extension: _, + } = engine.config().features; + + Metadata { + target: engine.compiler().triple().to_string(), + shared_flags: engine.compiler().flags(), + isa_flags: engine.compiler().isa_flags(), + tunables: engine.config().tunables.clone(), + features: WasmFeatures { + reference_types, + multi_value, + bulk_memory, + component_model, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + relaxed_simd, + extended_const, + }, + } + } + + fn check_compatible(mut self, engine: &Engine) -> Result<()> { + self.check_triple(engine)?; + self.check_shared_flags(engine)?; + self.check_isa_flags(engine)?; + self.check_tunables(&engine.config().tunables)?; + self.check_features(&engine.config().features)?; + Ok(()) + } + + fn check_triple(&self, engine: &Engine) -> Result<()> { + let engine_target = engine.target(); + let module_target = + target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; + + if module_target.architecture != engine_target.architecture { + bail!( + "Module was compiled for architecture '{}'", + module_target.architecture + ); + } + + if module_target.operating_system != engine_target.operating_system { + bail!( + "Module was compiled for operating system '{}'", + module_target.operating_system + ); + } + + Ok(()) + } + + fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> { + for (name, val) in self.shared_flags.iter() { + engine + .check_compatible_with_shared_flag(name, val) + .map_err(|s| anyhow::Error::msg(s)) + .context("compilation settings of module incompatible with native host")?; + } + Ok(()) + } + + fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> { + for (name, val) in self.isa_flags.iter() { + engine + .check_compatible_with_isa_flag(name, val) + .map_err(|s| anyhow::Error::msg(s)) + .context("compilation settings of module incompatible with native host")?; + } + 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(&mut self, other: &Tunables) -> Result<()> { + let Tunables { + static_memory_bound, + static_memory_offset_guard_size, + dynamic_memory_offset_guard_size, + generate_native_debuginfo, + parse_wasm_debuginfo, + consume_fuel, + epoch_interruption, + static_memory_bound_is_maximum, + guard_before_linear_memory, + + // This doesn't affect compilation, it's just a runtime setting. + dynamic_memory_growth_reserve: _, + + // This does technically affect compilation but modules with/without + // trap information can be loaded into engines with the opposite + // setting just fine (it's just a section in the compiled file and + // whether it's present or not) + generate_address_map: _, + + // Just a debugging aid, doesn't affect functionality at all. + debug_adapter_modules: _, + } = self.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(consume_fuel, other.consume_fuel, "fuel support")?; + Self::check_bool( + epoch_interruption, + other.epoch_interruption, + "epoch interruption", + )?; + Self::check_bool( + static_memory_bound_is_maximum, + other.static_memory_bound_is_maximum, + "pooling allocation support", + )?; + Self::check_bool( + guard_before_linear_memory, + other.guard_before_linear_memory, + "guard before linear memory", + )?; + + Ok(()) + } + + fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> { + let WasmFeatures { + reference_types, + multi_value, + bulk_memory, + component_model, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + relaxed_simd, + extended_const, + } = self.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( + component_model, + other.component_model, + "WebAssembly component model 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", + )?; + Self::check_bool( + extended_const, + other.extended_const, + "WebAssembly extended-const support", + )?; + Self::check_bool( + relaxed_simd, + other.relaxed_simd, + "WebAssembly relaxed-simd support", + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Config; + + #[test] + fn test_architecture_mismatch() -> Result<()> { + let engine = Engine::default(); + let mut metadata = Metadata::new(&engine); + metadata.target = "unknown-generic-linux".to_string(); + + match metadata.check_compatible(&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 mut metadata = Metadata::new(&engine); + + metadata.target = format!( + "{}-generic-unknown", + target_lexicon::Triple::host().architecture + ); + + match metadata.check_compatible(&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 mut metadata = Metadata::new(&engine); + + metadata + .shared_flags + .insert("avoid_div_traps".to_string(), FlagValue::Bool(false)); + + match metadata.check_compatible(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert!(format!("{:?}", e).starts_with( + "\ +compilation settings of module incompatible with native host + +Caused by: + setting \"avoid_div_traps\" is configured to Bool(false) which is not supported" + )), + } + + Ok(()) + } + + #[test] + fn test_isa_flags_mismatch() -> Result<()> { + let engine = Engine::default(); + let mut metadata = Metadata::new(&engine); + + metadata + .isa_flags + .insert("not_a_flag".to_string(), FlagValue::Bool(true)); + + match metadata.check_compatible(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert!(format!("{:?}", e).starts_with( + "\ +compilation settings of module incompatible with native host + +Caused by: + cannot test if target-specific flag \"not_a_flag\" is available at runtime", + )), + } + + Ok(()) + } + + #[test] + fn test_tunables_int_mismatch() -> Result<()> { + let engine = Engine::default(); + let mut metadata = Metadata::new(&engine); + + metadata.tunables.static_memory_offset_guard_size = 0; + + match metadata.check_compatible(&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.epoch_interruption(true); + + let engine = Engine::new(&config)?; + let mut metadata = Metadata::new(&engine); + metadata.tunables.epoch_interruption = false; + + match metadata.check_compatible(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled without epoch interruption but it is enabled for the host" + ), + } + + let mut config = Config::new(); + config.epoch_interruption(false); + + let engine = Engine::new(&config)?; + let mut metadata = Metadata::new(&engine); + metadata.tunables.epoch_interruption = true; + + match metadata.check_compatible(&engine) { + Ok(_) => unreachable!(), + Err(e) => assert_eq!( + e.to_string(), + "Module was compiled with epoch interruption 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 mut metadata = Metadata::new(&engine); + metadata.features.simd = false; + + match metadata.check_compatible(&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 mut metadata = Metadata::new(&engine); + metadata.features.simd = true; + + match metadata.check_compatible(&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/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 1b35f4f06a..f5123ac4f1 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -28,7 +28,6 @@ mod serialization; pub use registry::{is_wasm_trap_pc, ModuleRegistry}; #[cfg(feature = "component-model")] pub use registry::{register_component, unregister_component}; -pub use serialization::SerializedModule; /// A compiled WebAssembly module, ready to be instantiated. /// @@ -288,7 +287,7 @@ impl Module { cfg_if::cfg_if! { if #[cfg(feature = "cache")] { let state = (HashedEngineCompileEnv(engine), binary); - let (mmap, info, types) = wasmtime_cache::ModuleCacheEntry::new( + let (mmap, info_and_types) = wasmtime_cache::ModuleCacheEntry::new( "wasmtime", engine.cache_config(), ) @@ -299,28 +298,23 @@ impl Module { |(engine, wasm)| Module::build_artifacts(engine.0, wasm), // Implementation of how to serialize artifacts - |(engine, _wasm), (mmap, _info, types)| { - SerializedModule::from_artifacts( - engine.0, - mmap, - types, - ).to_bytes(&engine.0.config().module_version).ok() + |(_engine, _wasm), (mmap, _info_and_types)| { + Some(mmap.to_vec()) }, // Cache hit, deserialize the provided artifacts |(engine, _wasm), serialized_bytes| { - SerializedModule::from_bytes(&serialized_bytes, &engine.0.config().module_version) - .ok()? - .into_parts(engine.0) - .ok() + let mmap = engine.0.load_mmap_bytes(&serialized_bytes).ok()?; + Some((mmap, None)) }, )?; } else { - let (mmap, info, types) = Module::build_artifacts(engine, binary)?; + let (mmap, info_and_types) = Module::build_artifacts(engine, binary)?; } }; - Self::from_parts(engine, mmap, info, types) + let info_and_types = info_and_types.map(|(info, types)| (info, types.into())); + Self::from_parts(engine, mmap, info_and_types) } /// Converts an input binary-encoded WebAssembly module to compilation @@ -341,7 +335,7 @@ impl Module { pub(crate) fn build_artifacts( engine: &Engine, wasm: &[u8], - ) -> Result<(MmapVec, Option, ModuleTypes)> { + ) -> Result<(MmapVec, Option<(CompiledModuleInfo, ModuleTypes)>)> { let tunables = &engine.config().tunables; // First a `ModuleEnvironment` is created which records type information @@ -357,7 +351,7 @@ impl Module { .context("failed to parse WebAssembly module")?; let types = types.finish(); let (mmap, info) = Module::compile_functions(engine, translation, &types)?; - Ok((mmap, info, types)) + Ok((mmap, Some((info, types)))) } #[cfg(compiler)] @@ -365,7 +359,7 @@ impl Module { engine: &Engine, mut translation: ModuleTranslation<'_>, types: &ModuleTypes, - ) -> Result<(MmapVec, Option)> { + ) -> Result<(MmapVec, CompiledModuleInfo)> { let tunables = &engine.config().tunables; let functions = mem::take(&mut translation.function_body_inputs); let functions = functions.into_iter().collect::>(); @@ -428,16 +422,23 @@ impl Module { // table lazy init. translation.try_func_table_init(); - let (mmap, info) = wasmtime_jit::finish_compile( - translation, - obj, - funcs, - trampolines, - tunables, - engine.compiler().is_branch_protection_enabled(), - )?; + // Insert `Engine` and type-level information into the compiled + // artifact so if this module is deserialized later it contains all + // information necessary. + // + // Note that `append_compiler_info` and `append_types` here in theory + // can both be skipped if this module will never get serialized. + // They're only used during deserialization and not during runtime for + // the module itself. Currently there's no need for that, however, so + // it's left as an exercise for later. + engine.append_compiler_info(&mut obj); + engine.append_bti(&mut obj); + serialization::append_types(types, &mut obj); - Ok((mmap, Some(info))) + let (mmap, info) = + wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?; + + Ok((mmap, info)) } /// Deserializes an in-memory compiled module previously created with @@ -483,8 +484,8 @@ impl Module { /// blobs across versions of wasmtime you can be safely guaranteed that /// future versions of wasmtime will reject old cache entries). pub unsafe fn deserialize(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { - let module = SerializedModule::from_bytes(bytes.as_ref(), &engine.config().module_version)?; - module.into_module(engine) + let mmap = engine.load_mmap_bytes(bytes.as_ref())?; + Module::from_parts(engine, mmap, None) } /// Same as [`deserialize`], except that the contents of `path` are read to @@ -511,16 +512,19 @@ impl Module { /// reflect the current state of the file, not necessarily the origianl /// state of the file. pub unsafe fn deserialize_file(engine: &Engine, path: impl AsRef) -> Result { - let module = SerializedModule::from_file(path.as_ref(), &engine.config().module_version)?; - module.into_module(engine) + let mmap = engine.load_mmap_file(path.as_ref())?; + Module::from_parts(engine, mmap, None) } pub(crate) fn from_parts( engine: &Engine, mmap: MmapVec, - info: Option, - types: impl Into, + info_and_types: Option<(CompiledModuleInfo, Types)>, ) -> Result { + let (info, types) = match info_and_types { + Some((info, types)) => (Some(info), types), + None => (None, serialization::deserialize_types(&mmap)?.into()), + }; let module = Arc::new(CompiledModule::from_artifacts( mmap, info, @@ -531,7 +535,6 @@ impl Module { // Validate the module can be used with the current allocator engine.allocator().validate(module.module())?; - let types = types.into(); let signatures = SignatureCollection::new_for_module( engine.signatures(), types.module_types(), @@ -612,7 +615,7 @@ impl Module { #[cfg(compiler)] #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn serialize(&self) -> Result> { - SerializedModule::new(self).to_bytes(&self.inner.engine.config().module_version) + Ok(self.compiled_module().mmap().to_vec()) } pub(crate) fn compiled_module(&self) -> &CompiledModule { diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index bb0d7fd401..21d4efa58f 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -1,739 +1,45 @@ -//! Implements module serialization. +//! Support for serializing type information for a `Module`. //! -//! This module implements the serialization format for `wasmtime::Module`. -//! This includes both the binary format of the final artifact as well as -//! validation on ingestion of artifacts. +//! Wasmtime AOT compiled artifacts are ELF files where relevant data is stored +//! in relevant sections. This module implements the serialization format for +//! type information, or the `ModuleTypes` structure. //! -//! There are two main pieces of data associated with a binary artifact: +//! This structure lives in a section of the final artifact at this time. It is +//! appended after compilation has otherwise completed and additionally is +//! deserialized from the entirety of the section. //! -//! 1. The compiled module image, currently an ELF file. -//! 2. Compilation metadata for the module, including the `ModuleTypes` -//! information. This metadata is validated for compilation settings. -//! -//! Compiled modules are, at this time, represented as an ELF file. This ELF -//! file contains all the necessary data needed to decode a module, and -//! conveniently also handles things like alignment so we can actually directly -//! `mmap` compilation artifacts from disk. -//! -//! With this in mind, the current serialization format is as follows: -//! -//! * First the ELF image for the compiled module starts the artifact. This -//! helps developers use standard ELF-reading utilities like `objdump` to poke -//! around and see what's inside the compiled image. -//! -//! * After the ELF file is a number of fields: -//! -//! 1. The `HEADER` value -//! 2. A byte indicating how long the next field is -//! 3. A version string of the length of the previous byte value -//! 4. A `bincode`-encoded `Metadata` structure. -//! -//! This is hoped to help distinguish easily Wasmtime-based ELF files from -//! other random ELF files, as well as provide better error messages for -//! using wasmtime artifacts across versions. -//! -//! Note that the structure of the ELF format is what enables this -//! representation. We can have trailing data after an ELF file which isn't read -//! by any parsing of the ELF itself, which provides a convenient location for -//! the metadata information to go. -//! -//! This format is implemented by the `to_bytes` and `from_mmap` function. +//! Implementation details are "just bincode it all" right now with no further +//! clever tricks about representation. Currently this works out more-or-less +//! ok since the type information is typically relatively small per-module. -use crate::{Engine, Module, ModuleVersionStrategy}; -use anyhow::{anyhow, bail, Context, Result}; -use object::read::elf::FileHeader; -use object::Bytes; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::path::Path; -use std::str::FromStr; -use wasmtime_environ::{FlagValue, ModuleTypes, Tunables}; -use wasmtime_jit::{subslice_range, CompiledModuleInfo}; +use anyhow::{anyhow, Result}; +use object::write::{Object, StandardSegment}; +use object::{File, Object as _, ObjectSection, SectionKind}; +use wasmtime_environ::ModuleTypes; use wasmtime_runtime::MmapVec; -const HEADER: &[u8] = b"\0wasmtime-aot"; +const ELF_WASM_TYPES: &str = ".wasmtime.types"; -// 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 component_model: 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, - pub relaxed_simd: bool, - pub extended_const: bool, +pub fn append_types(types: &ModuleTypes, obj: &mut Object<'_>) { + let section = obj.add_section( + obj.segment_name(StandardSegment::Data).to_vec(), + ELF_WASM_TYPES.as_bytes().to_vec(), + SectionKind::ReadOnlyData, + ); + let data = bincode::serialize(types).unwrap(); + obj.set_section_data(section, data, 1); } -impl From<&wasmparser::WasmFeatures> for WasmFeatures { - fn from(other: &wasmparser::WasmFeatures) -> Self { - let wasmparser::WasmFeatures { - reference_types, - multi_value, - bulk_memory, - component_model, - simd, - threads, - tail_call, - deterministic_only, - multi_memory, - exceptions, - memory64, - relaxed_simd, - extended_const, - - // Always on; we don't currently have knobs for these. - mutable_global: _, - saturating_float_to_int: _, - sign_extension: _, - } = *other; - - Self { - reference_types, - multi_value, - bulk_memory, - component_model, - simd, - threads, - tail_call, - deterministic_only, - multi_memory, - exceptions, - memory64, - relaxed_simd, - extended_const, - } - } -} - -// 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 as_ref(&self) -> &T { - match self { - MyCow::Owned(val) => val, - MyCow::Borrowed(val) => val, - } - } - 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)?)) - } -} - -pub struct SerializedModule<'a> { - artifacts: MyCow<'a, MmapVec>, - metadata: Metadata<'a>, -} - -#[derive(Serialize, Deserialize)] -struct Metadata<'a> { - target: String, - shared_flags: BTreeMap, - isa_flags: BTreeMap, - tunables: Tunables, - features: WasmFeatures, - types: MyCow<'a, ModuleTypes>, -} - -impl<'a> SerializedModule<'a> { - #[cfg(compiler)] - pub fn new(module: &'a Module) -> Self { - Self::with_data( - module.engine(), - MyCow::Borrowed(module.compiled_module().mmap()), - MyCow::Borrowed(module.types()), - ) - } - - #[cfg(compiler)] - pub fn from_artifacts(engine: &Engine, artifacts: &'a MmapVec, types: &'a ModuleTypes) -> Self { - Self::with_data(engine, MyCow::Borrowed(artifacts), MyCow::Borrowed(types)) - } - - #[cfg(compiler)] - fn with_data( - engine: &Engine, - artifacts: MyCow<'a, MmapVec>, - types: MyCow<'a, ModuleTypes>, - ) -> Self { - Self { - artifacts, - metadata: Metadata { - target: engine.compiler().triple().to_string(), - shared_flags: engine.compiler().flags(), - isa_flags: engine.compiler().isa_flags(), - tunables: engine.config().tunables.clone(), - features: (&engine.config().features).into(), - types, - }, - } - } - - pub fn into_module(self, engine: &Engine) -> Result { - let (mmap, info, types) = self.into_parts(engine)?; - Module::from_parts(engine, mmap, info, types) - } - - pub fn into_parts( - mut self, - engine: &Engine, - ) -> Result<(MmapVec, Option, ModuleTypes)> { - // Verify that the compilation settings in the engine match the - // compilation settings of the module that's being loaded. - self.check_triple(engine)?; - self.check_shared_flags(engine)?; - self.check_isa_flags(engine)?; - - self.check_tunables(&engine.config().tunables)?; - self.check_features(&engine.config().features)?; - - let module = self.artifacts.unwrap_owned(); - - Ok((module, None, self.metadata.types.unwrap_owned())) - } - - pub fn to_bytes(&self, version_strat: &ModuleVersionStrategy) -> Result> { - // Start off with a copy of the ELF image. - let mut ret = self.artifacts.as_ref().to_vec(); - - // Append the bincode-encoded `Metadata` section with a few other guards - // to help give better error messages during deserialization if - // something goes wrong. - ret.extend_from_slice(HEADER); - let version = match version_strat { - ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"), - ModuleVersionStrategy::Custom(c) => &c, - ModuleVersionStrategy::None => "", - }; - // This precondition is checked in Config::module_version: - assert!( - version.len() < 256, - "package version must be less than 256 bytes" - ); - ret.push(version.len() as u8); - ret.extend_from_slice(version.as_bytes()); - bincode::serialize_into(&mut ret, &self.metadata)?; - - Ok(ret) - } - - pub fn from_bytes(bytes: &[u8], version_strat: &ModuleVersionStrategy) -> Result { - Self::from_mmap(MmapVec::from_slice(bytes)?, version_strat) - } - - pub fn from_file(path: &Path, version_strat: &ModuleVersionStrategy) -> Result { - Self::from_mmap( - MmapVec::from_file(path).with_context(|| { - format!("failed to create file mapping for: {}", path.display()) - })?, - version_strat, - ) - } - - pub fn from_mmap(mut mmap: MmapVec, version_strat: &ModuleVersionStrategy) -> Result { - // First validate that this is at least somewhat an elf file within - // `mmap` and additionally skip to the end of the elf file to find our - // metadata. - let metadata = data_after_elf(&mut mmap)?; - - // The metadata has a few guards up front which we process first, and - // eventually this bottoms out in a `bincode::deserialize` call. - let metadata = metadata - .strip_prefix(HEADER) - .ok_or_else(|| anyhow!("bytes are not a compatible serialized wasmtime module"))?; - if metadata.is_empty() { - bail!("serialized data data is empty"); - } - let version_len = metadata[0] as usize; - if metadata.len() < version_len + 1 { - bail!("serialized data is malformed"); - } - - match version_strat { - ModuleVersionStrategy::WasmtimeVersion => { - let version = std::str::from_utf8(&metadata[1..1 + version_len])?; - if version != env!("CARGO_PKG_VERSION") { - bail!( - "Module was compiled with incompatible Wasmtime version '{}'", - version - ); - } - } - ModuleVersionStrategy::Custom(v) => { - let version = std::str::from_utf8(&metadata[1..1 + version_len])?; - if version != v { - bail!( - "Module was compiled with incompatible version '{}'", - version - ); - } - } - ModuleVersionStrategy::None => { /* ignore the version info, accept all */ } - } - - let metadata = bincode::deserialize::(&metadata[1 + version_len..]) - .context("deserialize compilation artifacts")?; - - return Ok(SerializedModule { - artifacts: MyCow::Owned(mmap), - metadata, - }); - - /// This function will return the trailing data behind the ELF file - /// parsed from `data` which is where we find our metadata section. - fn data_after_elf(mmap: &mut MmapVec) -> Result { - use object::NativeEndian as NE; - // There's not actually a great utility for figuring out where - // the end of an ELF file is in the `object` crate. In lieu of that - // we build our own which leverages the format of ELF files, which - // is that the header comes first, that tells us where the section - // headers are, and for our ELF files the end of the file is the - // end of the section headers. - let data = &mmap[..]; - let mut bytes = Bytes(data); - let header = bytes - .read::>() - .map_err(|()| anyhow!("artifact truncated, can't read header"))?; - if !header.is_supported() { - bail!("invalid elf header"); - } - let sections = header - .section_headers(NE, data) - .context("failed to read section headers")?; - let range = subslice_range(object::bytes_of_slice(sections), data); - Ok(mmap.split_off(range.end)) - } - } - - fn check_triple(&self, engine: &Engine) -> Result<()> { - let engine_target = engine.target(); - let module_target = - target_lexicon::Triple::from_str(&self.metadata.target).map_err(|e| anyhow!(e))?; - - if module_target.architecture != engine_target.architecture { - bail!( - "Module was compiled for architecture '{}'", - module_target.architecture - ); - } - - if module_target.operating_system != engine_target.operating_system { - bail!( - "Module was compiled for operating system '{}'", - module_target.operating_system - ); - } - - Ok(()) - } - - fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> { - for (name, val) in self.metadata.shared_flags.iter() { - engine - .check_compatible_with_shared_flag(name, val) - .map_err(|s| anyhow::Error::msg(s)) - .context("compilation settings of module incompatible with native host")?; - } - Ok(()) - } - - fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> { - for (name, val) in self.metadata.isa_flags.iter() { - engine - .check_compatible_with_isa_flag(name, val) - .map_err(|s| anyhow::Error::msg(s)) - .context("compilation settings of module incompatible with native host")?; - } - 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(&mut self, other: &Tunables) -> Result<()> { - let Tunables { - static_memory_bound, - static_memory_offset_guard_size, - dynamic_memory_offset_guard_size, - generate_native_debuginfo, - parse_wasm_debuginfo, - consume_fuel, - epoch_interruption, - static_memory_bound_is_maximum, - guard_before_linear_memory, - - // This doesn't affect compilation, it's just a runtime setting. - dynamic_memory_growth_reserve: _, - - // This does technically affect compilation but modules with/without - // trap information can be loaded into engines with the opposite - // setting just fine (it's just a section in the compiled file and - // whether it's present or not) - generate_address_map: _, - - // Just a debugging aid, doesn't affect functionality at all. - debug_adapter_modules: _, - } = self.metadata.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(consume_fuel, other.consume_fuel, "fuel support")?; - Self::check_bool( - epoch_interruption, - other.epoch_interruption, - "epoch interruption", - )?; - Self::check_bool( - static_memory_bound_is_maximum, - other.static_memory_bound_is_maximum, - "pooling allocation support", - )?; - Self::check_bool( - guard_before_linear_memory, - other.guard_before_linear_memory, - "guard before linear memory", - )?; - - Ok(()) - } - - fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> { - let WasmFeatures { - reference_types, - multi_value, - bulk_memory, - component_model, - simd, - threads, - tail_call, - deterministic_only, - multi_memory, - exceptions, - memory64, - relaxed_simd, - extended_const, - } = self.metadata.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( - component_model, - other.component_model, - "WebAssembly component model 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", - )?; - Self::check_bool( - extended_const, - other.extended_const, - "WebAssembly extended-const support", - )?; - Self::check_bool( - relaxed_simd, - other.relaxed_simd, - "WebAssembly relaxed-simd 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.metadata.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.metadata.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 - .metadata - .shared_flags - .insert("avoid_div_traps".to_string(), FlagValue::Bool(false)); - - match serialized.into_module(&engine) { - Ok(_) => unreachable!(), - Err(e) => assert!(format!("{:?}", e).starts_with( - "\ -compilation settings of module incompatible with native host - -Caused by: - setting \"avoid_div_traps\" is configured to Bool(false) which is not supported" - )), - } - - Ok(()) - } - - #[test] - fn test_isa_flags_mismatch() -> Result<()> { - let engine = Engine::default(); - let module = Module::new(&engine, "(module)")?; - - let mut serialized = SerializedModule::new(&module); - - serialized - .metadata - .isa_flags - .insert("not_a_flag".to_string(), FlagValue::Bool(true)); - - match serialized.into_module(&engine) { - Ok(_) => unreachable!(), - Err(e) => assert!(format!("{:?}", e).starts_with( - "\ -compilation settings of module incompatible with native host - -Caused by: - cannot test if target-specific flag \"not_a_flag\" is available at runtime", - )), - } - - 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.metadata.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.epoch_interruption(true); - - let engine = Engine::new(&config)?; - let module = Module::new(&engine, "(module)")?; - - let mut serialized = SerializedModule::new(&module); - serialized.metadata.tunables.epoch_interruption = false; - - match serialized.into_module(&engine) { - Ok(_) => unreachable!(), - Err(e) => assert_eq!( - e.to_string(), - "Module was compiled without epoch interruption but it is enabled for the host" - ), - } - - let mut config = Config::new(); - config.epoch_interruption(false); - - let engine = Engine::new(&config)?; - let module = Module::new(&engine, "(module)")?; - - let mut serialized = SerializedModule::new(&module); - serialized.metadata.tunables.epoch_interruption = true; - - match serialized.into_module(&engine) { - Ok(_) => unreachable!(), - Err(e) => assert_eq!( - e.to_string(), - "Module was compiled with epoch interruption 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.metadata.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.metadata.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(()) - } +pub fn deserialize_types(mmap: &MmapVec) -> Result { + // Ideally we'd only `File::parse` once and avoid the linear + // `section_by_name` search here but the general serialization code isn't + // structured well enough to make this easy and additionally it's not really + // a perf issue right now so doing that is left for another day's + // refactoring. + let obj = File::parse(&mmap[..])?; + let data = obj + .section_by_name(ELF_WASM_TYPES) + .ok_or_else(|| anyhow!("failed to find section `{ELF_WASM_TYPES}`"))? + .data()?; + Ok(bincode::deserialize(data)?) } diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index de0d0f4306..8406873708 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -113,12 +113,14 @@ where stub_fn:: as usize, &mut obj, )?; + engine.append_compiler_info(&mut obj); + engine.append_bti(&mut obj); let obj = wasmtime_jit::mmap_vec_from_obj(obj)?; // Copy the results of JIT compilation into executable memory, and this will // also take care of unwind table registration. let mut code_memory = CodeMemory::new(obj); - let code = code_memory.publish(engine.compiler().is_branch_protection_enabled())?; + let code = code_memory.publish()?; register_trampolines(engine.profiler(), &code.obj);