Refactor metadata storage in AOT artifacts (#5153)
* Refactor metadata storage in AOT artifacts This commit is a reorganization of how metadata is stored in Wasmtime's compiled artifacts. Currently Wasmtime's ELF artifacts have data appended after them to contain metadata about the `Engine` as well as type information for the module itself. This extra data at the end of the file is ignored by ELF-related utilities generally and is assembled during the module serialization process. In working on AOT-compiling components, though, I've discovered a number of issues with this: * Primarily it's possible to mistakenly change an artifact if it's deserialized and then serialized again. This issue is probably theoretical but the deserialized artifact records the `Engine` configuration at time of creation but when re-serializing that it serializes the current `Engine` state, not the original `Engine` state. * Additionally the serialization strategy here is tightly coupled to `Module` and its serialization format. While this makes sense it is not conducive for future refactorings to use a similar serialization format for components. The engine metadata, for example, does not necessarily need to be tied up with type information. * The storage for this extra metadata is a bit wonky by shoving it at the end of the ELF file. The original reason for this was to have a compiled artifact be multiple objects concatenated with each other to support serializing module-linking-using modules. Module linking is no longer a thing and I have since decided that for the component model all compilation artifacts will go into one object file to assist debugability. This means that the extra stick-it-at-the-end is no longer necessary. To solve these issues this commit splits up the `module/serialization.rs` file in two, mostly moving the logic to `engine/serialization.rs`. The engine serialization logic now handles everything related to `Engine` compatibility such as targets, compiler flags, wasm features, etc. The module serialization logic is now exclusively interested in type information. The engine metadata and serialized type information additionally live in sections of the final file now instead of at the end. This means that there are three primary `bincode`-encoded sections that are parsed on deserializing a file: 1. The `Engine`-specific metadata. This will be the same for both modules and components. 2. The `CompiledModuleInfo` structure. For core wasm there's just one of these but for the component model there will be multiple, one per core wasm module. 3. The type information. For core wasm this is a `ModuleTypes` but for a component this will be a `ComponentTypes`. No true functional change is expected from this commit. Binary artifacts might get inflated by a small handful of bytes due to using ELF sections to represent this now. A related change I made during this commit as well was the plumbing of the `is_branch_protection_enabled` flag. This is technically `Engine`-level metadata but I didn't want to plumb it all over the place as was done now, so instead a new section was added to the final binary just for this bti information. This means that it no longer needs to be a parameter to `CodeMemory::publish` and additionally is more amenable to a `Component`-is-just-one-object world where no single module owns this piece of metadata. * Exclude some functions in a cranelift-less build
This commit is contained in:
@@ -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<Publish<'_>> {
|
||||
pub fn publish(&mut self) -> Result<Publish<'_>> {
|
||||
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:
|
||||
//
|
||||
|
||||
@@ -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<DefinedFuncIndex, FunctionInfo>,
|
||||
trampolines: Vec<Trampoline>,
|
||||
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)?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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::<PrimaryMap<_, _>>())
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Vec<u8>> {
|
||||
#[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<MmapVec> {
|
||||
self.load_mmap(MmapVec::from_slice(bytes)?)
|
||||
}
|
||||
|
||||
pub(crate) fn load_mmap_file(&self, path: &Path) -> Result<MmapVec> {
|
||||
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<MmapVec> {
|
||||
serialization::check_compatible(self, &mmap)?;
|
||||
Ok(mmap)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Engine {
|
||||
|
||||
590
crates/wasmtime/src/engine/serialization.rs
Normal file
590
crates/wasmtime/src/engine/serialization.rs
Normal file
@@ -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::<Metadata>(data)?.check_compatible(engine)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Metadata {
|
||||
target: String,
|
||||
shared_flags: BTreeMap<String, FlagValue>,
|
||||
isa_flags: BTreeMap<String, FlagValue>,
|
||||
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<T: Eq + std::fmt::Display>(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(())
|
||||
}
|
||||
}
|
||||
@@ -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<CompiledModuleInfo>, 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<CompiledModuleInfo>)> {
|
||||
) -> Result<(MmapVec, CompiledModuleInfo)> {
|
||||
let tunables = &engine.config().tunables;
|
||||
let functions = mem::take(&mut translation.function_body_inputs);
|
||||
let functions = functions.into_iter().collect::<Vec<_>>();
|
||||
@@ -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<Module> {
|
||||
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<Path>) -> Result<Module> {
|
||||
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<CompiledModuleInfo>,
|
||||
types: impl Into<Types>,
|
||||
info_and_types: Option<(CompiledModuleInfo, Types)>,
|
||||
) -> Result<Self> {
|
||||
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<Vec<u8>> {
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
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<S>(&self, dst: S) -> Result<S::Ok, S::Error>
|
||||
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<D>(src: D) -> Result<Self, D::Error>
|
||||
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<String, FlagValue>,
|
||||
isa_flags: BTreeMap<String, FlagValue>,
|
||||
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<Module> {
|
||||
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<CompiledModuleInfo>, 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<Vec<u8>> {
|
||||
// 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"
|
||||
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,
|
||||
);
|
||||
ret.push(version.len() as u8);
|
||||
ret.extend_from_slice(version.as_bytes());
|
||||
bincode::serialize_into(&mut ret, &self.metadata)?;
|
||||
|
||||
Ok(ret)
|
||||
let data = bincode::serialize(types).unwrap();
|
||||
obj.set_section_data(section, data, 1);
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8], version_strat: &ModuleVersionStrategy) -> Result<Self> {
|
||||
Self::from_mmap(MmapVec::from_slice(bytes)?, version_strat)
|
||||
}
|
||||
|
||||
pub fn from_file(path: &Path, version_strat: &ModuleVersionStrategy) -> Result<Self> {
|
||||
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<Self> {
|
||||
// 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>(&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<MmapVec> {
|
||||
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::<object::elf::FileHeader64<NE>>()
|
||||
.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<T: Eq + std::fmt::Display>(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<ModuleTypes> {
|
||||
// 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)?)
|
||||
}
|
||||
|
||||
@@ -113,12 +113,14 @@ where
|
||||
stub_fn::<F> 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user