use crate::{
code::CodeObject,
resources::ResourcesRequired,
signatures::SignatureCollection,
types::{ExportType, ExternType, ImportType},
Engine,
};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use std::fs;
use std::mem;
use std::ops::Range;
use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
use wasmparser::{Parser, ValidPayload, Validator};
use wasmtime_environ::{
DefinedFuncIndex, DefinedMemoryIndex, HostPtr, ModuleEnvironment, ModuleTypes, ObjectKind,
VMOffsets,
};
use wasmtime_jit::{CodeMemory, CompiledModule, CompiledModuleInfo};
use wasmtime_runtime::{
CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMArrayCallFunction,
VMNativeCallFunction, VMSharedSignatureIndex, VMWasmCallFunction,
};
mod registry;
pub use registry::{
is_wasm_trap_pc, register_code, unregister_code, ModuleRegistry, RegisteredModuleId,
};
#[derive(Clone)]
pub struct Module {
inner: Arc<ModuleInner>,
}
struct ModuleInner {
engine: Engine,
module: CompiledModule,
code: Arc<CodeObject>,
memory_images: OnceCell<Option<ModuleMemoryImages>>,
serializable: bool,
offsets: VMOffsets<HostPtr>,
}
impl std::fmt::Debug for Module {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Module")
.field("name", &self.name())
.finish_non_exhaustive()
}
}
impl Module {
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
let bytes = bytes.as_ref();
#[cfg(feature = "wat")]
let bytes = wat::parse_bytes(bytes)?;
Self::from_binary(engine, &bytes)
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
let file = file.as_ref();
match Self::new(
engine,
&fs::read(file)
.with_context(|| format!("failed to read input file: {}", file.display()))?,
) {
Ok(m) => Ok(m),
Err(e) => {
cfg_if::cfg_if! {
if #[cfg(feature = "wat")] {
let mut e = e.downcast::<wat::Error>()?;
e.set_path(file);
bail!(e)
} else {
Err(e)
}
}
}
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
engine
.check_compatible_with_native_host()
.context("compilation settings are not compatible with the native host")?;
cfg_if::cfg_if! {
if #[cfg(feature = "cache")] {
let state = (HashedEngineCompileEnv(engine), binary);
let (code, info_and_types) = wasmtime_cache::ModuleCacheEntry::new(
"wasmtime",
engine.cache_config(),
)
.get_data_raw(
&state,
|(engine, wasm)| -> Result<_> {
let (mmap, info) = Module::build_artifacts(engine.0, wasm)?;
let code = publish_mmap(mmap)?;
Ok((code, info))
},
|(_engine, _wasm), (code, _info_and_types)| {
Some(code.mmap().to_vec())
},
|(engine, _wasm), serialized_bytes| {
let code = engine.0.load_code_bytes(&serialized_bytes, ObjectKind::Module).ok()?;
Some((code, None))
},
)?;
} else {
let (mmap, info_and_types) = Module::build_artifacts(engine, binary)?;
let code = publish_mmap(mmap)?;
}
};
let info_and_types = info_and_types.map(|(info, types)| (info, types.into()));
return Self::from_parts(engine, code, info_and_types);
fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
let mut code = CodeMemory::new(mmap)?;
code.publish()?;
Ok(Arc::new(code))
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub unsafe fn from_trusted_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
let mmap = MmapVec::from_file(file.as_ref())?;
if &mmap[0..4] == b"\x7fELF" {
let code = engine.load_code(mmap, ObjectKind::Module)?;
return Module::from_parts(engine, code, None);
}
Module::new(engine, &*mmap)
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub(crate) fn build_artifacts(
engine: &Engine,
wasm: &[u8],
) -> Result<(MmapVec, Option<(CompiledModuleInfo, ModuleTypes)>)> {
use crate::compiler::CompileInputs;
let tunables = &engine.config().tunables;
let mut validator =
wasmparser::Validator::new_with_features(engine.config().features.clone());
let parser = wasmparser::Parser::new(0);
let mut types = Default::default();
let mut translation = ModuleEnvironment::new(tunables, &mut validator, &mut types)
.translate(parser, wasm)
.context("failed to parse WebAssembly module")?;
let functions = mem::take(&mut translation.function_body_inputs);
let compile_inputs = CompileInputs::for_module(&types, &translation, functions);
let unlinked_compile_outputs = compile_inputs.compile(engine)?;
let types = types.finish();
let (compiled_funcs, function_indices) = unlinked_compile_outputs.pre_link();
let mut object = engine.compiler().object(ObjectKind::Module)?;
engine.append_compiler_info(&mut object);
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = function_indices.link_and_append_code(
object,
engine,
compiled_funcs,
std::iter::once(translation).collect(),
)?;
let info = compilation_artifacts.unwrap_as_module_info();
object.serialize_info(&(&info, &types));
let mmap = object.finish()?;
Ok((mmap, Some((info, types))))
}
pub unsafe fn deserialize(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
let code = engine.load_code_bytes(bytes.as_ref(), ObjectKind::Module)?;
Module::from_parts(engine, code, None)
}
pub unsafe fn deserialize_file(engine: &Engine, path: impl AsRef<Path>) -> Result<Module> {
let code = engine.load_code_file(path.as_ref(), ObjectKind::Module)?;
Module::from_parts(engine, code, None)
}
fn from_parts(
engine: &Engine,
code_memory: Arc<CodeMemory>,
info_and_types: Option<(CompiledModuleInfo, ModuleTypes)>,
) -> Result<Self> {
let (info, types) = match info_and_types {
Some((info, types)) => (info, types),
None => bincode::deserialize(code_memory.wasmtime_info())?,
};
let signatures = SignatureCollection::new_for_module(engine.signatures(), &types);
let code = Arc::new(CodeObject::new(code_memory, signatures, types.into()));
Module::from_parts_raw(engine, code, info, true)
}
pub(crate) fn from_parts_raw(
engine: &Engine,
code: Arc<CodeObject>,
info: CompiledModuleInfo,
serializable: bool,
) -> Result<Self> {
let module = CompiledModule::from_artifacts(
code.code_memory().clone(),
info,
engine.profiler(),
engine.unique_id_allocator(),
)?;
let offsets = VMOffsets::new(HostPtr, module.module());
engine
.allocator()
.validate_module(module.module(), &offsets)?;
Ok(Self {
inner: Arc::new(ModuleInner {
engine: engine.clone(),
code,
memory_images: OnceCell::new(),
module,
serializable,
offsets,
}),
})
}
pub fn validate(engine: &Engine, binary: &[u8]) -> Result<()> {
let mut validator = Validator::new_with_features(engine.config().features);
let mut functions = Vec::new();
for payload in Parser::new(0).parse_all(binary) {
let payload = payload?;
if let ValidPayload::Func(a, b) = validator.payload(&payload)? {
functions.push((a, b));
}
if let wasmparser::Payload::Version { encoding, .. } = &payload {
if let wasmparser::Encoding::Component = encoding {
bail!("component passed to module validation");
}
}
}
engine.run_maybe_parallel(functions, |(validator, body)| {
validator.into_validator(Default::default()).validate(&body)
})?;
Ok(())
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub fn serialize(&self) -> Result<Vec<u8>> {
if !self.inner.serializable {
bail!("cannot serialize a module exported from a component");
}
Ok(self.compiled_module().mmap().to_vec())
}
pub(crate) fn compiled_module(&self) -> &CompiledModule {
&self.inner.module
}
fn code_object(&self) -> &Arc<CodeObject> {
&self.inner.code
}
pub(crate) fn env_module(&self) -> &wasmtime_environ::Module {
self.compiled_module().module()
}
pub(crate) fn types(&self) -> &ModuleTypes {
self.inner.code.module_types()
}
pub(crate) fn signatures(&self) -> &SignatureCollection {
self.inner.code.signatures()
}
pub fn name(&self) -> Option<&str> {
self.compiled_module().module().name.as_deref()
}
pub fn imports<'module>(
&'module self,
) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module {
let module = self.compiled_module().module();
let types = self.types();
module
.imports()
.map(move |(module, field, ty)| ImportType::new(module, field, ty, types))
.collect::<Vec<_>>()
.into_iter()
}
pub fn exports<'module>(
&'module self,
) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module {
let module = self.compiled_module().module();
let types = self.types();
module.exports.iter().map(move |(name, entity_index)| {
ExportType::new(name, module.type_of(*entity_index), types)
})
}
pub fn get_export(&self, name: &str) -> Option<ExternType> {
let module = self.compiled_module().module();
let entity_index = module.exports.get(name)?;
Some(ExternType::from_wasmtime(
self.types(),
&module.type_of(*entity_index),
))
}
pub fn engine(&self) -> &Engine {
&self.inner.engine
}
pub fn resources_required(&self) -> ResourcesRequired {
let em = self.env_module();
let num_memories = u32::try_from(em.memory_plans.len() - em.num_imported_memories).unwrap();
let max_initial_memory_size = em
.memory_plans
.values()
.skip(em.num_imported_memories)
.map(|plan| plan.memory.minimum)
.max();
let num_tables = u32::try_from(em.table_plans.len() - em.num_imported_tables).unwrap();
let max_initial_table_size = em
.table_plans
.values()
.skip(em.num_imported_tables)
.map(|plan| plan.table.minimum)
.max();
ResourcesRequired {
num_memories,
max_initial_memory_size,
num_tables,
max_initial_table_size,
}
}
pub(crate) fn runtime_info(&self) -> Arc<dyn wasmtime_runtime::ModuleRuntimeInfo> {
self.inner.clone()
}
pub(crate) fn module_info(&self) -> &dyn wasmtime_runtime::ModuleInfo {
&*self.inner
}
pub fn image_range(&self) -> Range<usize> {
self.compiled_module().image_range()
}
pub fn initialize_copy_on_write_image(&self) -> Result<()> {
self.inner.memory_images()?;
Ok(())
}
pub fn address_map<'a>(&'a self) -> Option<impl Iterator<Item = (usize, Option<u32>)> + 'a> {
Some(
wasmtime_environ::iterate_address_map(
self.code_object().code_memory().address_map_data(),
)?
.map(|(offset, file_pos)| (offset as usize, file_pos.file_offset())),
)
}
pub fn text(&self) -> &[u8] {
self.code_object().code_memory().text()
}
pub fn function_locations<'a>(&'a self) -> impl ExactSizeIterator<Item = (usize, usize)> + 'a {
self.compiled_module().finished_functions().map(|(f, _)| {
let loc = self.compiled_module().func_loc(f);
(loc.start as usize, loc.length as usize)
})
}
pub(crate) fn id(&self) -> CompiledModuleId {
self.inner.module.unique_id()
}
}
impl ModuleInner {
fn memory_images(&self) -> Result<Option<&ModuleMemoryImages>> {
let images = self
.memory_images
.get_or_try_init(|| memory_images(&self.engine, &self.module))?
.as_ref();
Ok(images)
}
}
impl Drop for ModuleInner {
fn drop(&mut self) {
self.engine
.allocator()
.purge_module(self.module.unique_id());
}
}
fn _assert_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Module>();
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub(crate) struct HashedEngineCompileEnv<'a>(pub &'a Engine);
#[cfg(any(feature = "cranelift", feature = "winch"))]
impl std::hash::Hash for HashedEngineCompileEnv<'_> {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
let compiler = self.0.compiler();
compiler.triple().hash(hasher);
compiler.flags().hash(hasher);
compiler.isa_flags().hash(hasher);
let config = self.0.config();
config.tunables.hash(hasher);
config.features.hash(hasher);
config.wmemcheck.hash(hasher);
config.module_version.hash(hasher);
}
}
impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
self.module.module()
}
fn function(&self, index: DefinedFuncIndex) -> NonNull<VMWasmCallFunction> {
let ptr = self
.module
.finished_function(index)
.as_ptr()
.cast::<VMWasmCallFunction>()
.cast_mut();
NonNull::new(ptr).unwrap()
}
fn native_to_wasm_trampoline(
&self,
index: DefinedFuncIndex,
) -> Option<NonNull<VMNativeCallFunction>> {
let ptr = self
.module
.native_to_wasm_trampoline(index)?
.as_ptr()
.cast::<VMNativeCallFunction>()
.cast_mut();
Some(NonNull::new(ptr).unwrap())
}
fn array_to_wasm_trampoline(&self, index: DefinedFuncIndex) -> Option<VMArrayCallFunction> {
let ptr = self.module.array_to_wasm_trampoline(index)?.as_ptr();
Some(unsafe { mem::transmute::<*const u8, VMArrayCallFunction>(ptr) })
}
fn wasm_to_native_trampoline(
&self,
signature: VMSharedSignatureIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
let sig = self.code.signatures().local_signature(signature)?;
let ptr = self
.module
.wasm_to_native_trampoline(sig)
.as_ptr()
.cast::<VMWasmCallFunction>()
.cast_mut();
Some(NonNull::new(ptr).unwrap())
}
fn memory_image(&self, memory: DefinedMemoryIndex) -> Result<Option<&Arc<MemoryImage>>> {
let images = self.memory_images()?;
Ok(images.and_then(|images| images.get_memory_image(memory)))
}
fn unique_id(&self) -> Option<CompiledModuleId> {
Some(self.module.unique_id())
}
fn wasm_data(&self) -> &[u8] {
self.module.code_memory().wasm_data()
}
fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
self.code.signatures().as_module_map().values().as_slice()
}
fn offsets(&self) -> &VMOffsets<HostPtr> {
&self.offsets
}
}
impl wasmtime_runtime::ModuleInfo for ModuleInner {
fn lookup_stack_map(&self, pc: usize) -> Option<&wasmtime_environ::StackMap> {
let text_offset = pc - self.module.text().as_ptr() as usize;
let (index, func_offset) = self.module.func_by_text_offset(text_offset)?;
let info = self.module.wasm_func_info(index);
let index = match info
.stack_maps
.binary_search_by_key(&func_offset, |i| i.code_offset)
{
Ok(i) => i,
Err(_) => return None,
};
Some(&info.stack_maps[index].stack_map)
}
}
pub(crate) struct BareModuleInfo {
module: Arc<wasmtime_environ::Module>,
one_signature: Option<VMSharedSignatureIndex>,
offsets: VMOffsets<HostPtr>,
}
impl BareModuleInfo {
pub(crate) fn empty(module: Arc<wasmtime_environ::Module>) -> Self {
BareModuleInfo::maybe_imported_func(module, None)
}
pub(crate) fn maybe_imported_func(
module: Arc<wasmtime_environ::Module>,
one_signature: Option<VMSharedSignatureIndex>,
) -> Self {
BareModuleInfo {
offsets: VMOffsets::new(HostPtr, &module),
module,
one_signature,
}
}
pub(crate) fn into_traitobj(self) -> Arc<dyn wasmtime_runtime::ModuleRuntimeInfo> {
Arc::new(self)
}
}
impl wasmtime_runtime::ModuleRuntimeInfo for BareModuleInfo {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
&self.module
}
fn function(&self, _index: DefinedFuncIndex) -> NonNull<VMWasmCallFunction> {
unreachable!()
}
fn array_to_wasm_trampoline(&self, _index: DefinedFuncIndex) -> Option<VMArrayCallFunction> {
unreachable!()
}
fn native_to_wasm_trampoline(
&self,
_index: DefinedFuncIndex,
) -> Option<NonNull<VMNativeCallFunction>> {
unreachable!()
}
fn wasm_to_native_trampoline(
&self,
_signature: VMSharedSignatureIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
unreachable!()
}
fn memory_image(&self, _memory: DefinedMemoryIndex) -> Result<Option<&Arc<MemoryImage>>> {
Ok(None)
}
fn unique_id(&self) -> Option<CompiledModuleId> {
None
}
fn wasm_data(&self) -> &[u8] {
&[]
}
fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
match &self.one_signature {
Some(id) => std::slice::from_ref(id),
None => &[],
}
}
fn offsets(&self) -> &VMOffsets<HostPtr> {
&self.offsets
}
}
fn memory_images(engine: &Engine, module: &CompiledModule) -> Result<Option<ModuleMemoryImages>> {
if !engine.config().memory_init_cow {
return Ok(None);
}
let mmap = if engine.config().force_memory_init_memfd {
None
} else {
Some(module.mmap())
};
ModuleMemoryImages::new(module.module(), module.code_memory().wasm_data(), mmap)
}
#[cfg(test)]
mod tests {
use crate::{Engine, Module};
use wasmtime_environ::MemoryInitialization;
#[test]
fn cow_on_by_default() {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"
(module
(memory 1)
(data (i32.const 100) "abcd")
)
"#,
)
.unwrap();
let init = &module.env_module().memory_initialization;
assert!(matches!(init, MemoryInitialization::Static { .. }));
}
}