use crate::prelude::*;
use crate::runtime::vm::{
CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMArrayCallFunction,
VMWasmCallFunction,
};
use crate::sync::OnceLock;
use crate::{
code::CodeObject,
code_memory::CodeMemory,
instantiate::CompiledModule,
resources::ResourcesRequired,
type_registry::TypeCollection,
types::{ExportType, ExternType, ImportType},
Engine,
};
use alloc::sync::Arc;
use anyhow::{bail, Result};
use core::fmt;
use core::mem;
use core::ops::Range;
use core::ptr::NonNull;
#[cfg(feature = "std")]
use std::path::Path;
use wasmparser::{Parser, ValidPayload, Validator};
use wasmtime_environ::{
CompiledModuleInfo, DefinedFuncIndex, DefinedMemoryIndex, EntityIndex, HostPtr, ModuleTypes,
ObjectKind, TypeTrace, VMOffsets, VMSharedTypeIndex,
};
mod registry;
pub use registry::{
get_wasm_trap, 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: OnceLock<Option<ModuleMemoryImages>>,
serializable: bool,
offsets: VMOffsets<HostPtr>,
}
impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Module")
.field("name", &self.name())
.finish_non_exhaustive()
}
}
impl fmt::Debug for ModuleInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ModuleInner")
.field("name", &self.module.module().name.as_ref())
.finish_non_exhaustive()
}
}
impl Module {
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
crate::CodeBuilder::new(engine)
.wasm(bytes.as_ref(), None)?
.compile_module()
}
#[cfg(all(feature = "std", any(feature = "cranelift", feature = "winch")))]
pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
crate::CodeBuilder::new(engine)
.wasm_file(file.as_ref())?
.compile_module()
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
crate::CodeBuilder::new(engine)
.wasm(binary, None)?
.wat(false)?
.compile_module()
}
#[cfg(all(feature = "std", 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);
}
crate::CodeBuilder::new(engine)
.wasm(&mmap, Some(file.as_ref()))?
.compile_module()
}
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)
}
#[cfg(feature = "std")]
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)
}
pub(crate) 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 => postcard::from_bytes(code_memory.wasmtime_info()).err2anyhow()?,
};
let signatures = TypeCollection::new_for_module(engine, &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: OnceLock::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.err2anyhow()?;
if let ValidPayload::Func(a, b) = validator.payload(&payload).err2anyhow()? {
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)
})
.err2anyhow()?;
Ok(())
}
#[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) -> &TypeCollection {
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();
let engine = self.engine();
module
.imports()
.map(move |(imp_mod, imp_field, mut ty)| {
ty.canonicalize_for_runtime_usage(&mut |i| {
self.signatures().shared_type(i).unwrap()
});
ImportType::new(imp_mod, imp_field, ty, types, engine)
})
.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();
let engine = self.engine();
module.exports.iter().map(move |(name, entity_index)| {
ExportType::new(name, module.type_of(*entity_index), types, engine)
})
}
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.engine(),
self.types(),
&module.type_of(*entity_index),
))
}
pub fn get_export_index(&self, name: &str) -> Option<ModuleExport> {
let compiled_module = self.compiled_module();
let module = compiled_module.module();
module
.exports
.get_full(name)
.map(|(export_name_index, _, &entity)| ModuleExport {
module: self.id(),
entity,
export_name_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 crate::runtime::vm::ModuleRuntimeInfo> {
self.inner.clone()
}
pub(crate) fn module_info(&self) -> &dyn crate::runtime::vm::ModuleInfo {
&*self.inner
}
pub fn image_range(&self) -> Range<*const u8> {
self.compiled_module().mmap().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 functions<'a>(&'a self) -> impl ExactSizeIterator<Item = ModuleFunction> + 'a {
let module = self.compiled_module();
module.finished_functions().map(|(idx, _)| {
let loc = module.func_loc(idx);
let idx = module.module().func_index(idx);
ModuleFunction {
index: idx,
name: module.func_name(idx).map(|n| n.to_string()),
offset: loc.start as usize,
len: loc.length as usize,
}
})
}
pub(crate) fn id(&self) -> CompiledModuleId {
self.inner.module.unique_id()
}
}
pub struct ModuleFunction {
pub index: wasmtime_environ::FuncIndex,
pub name: Option<String>,
pub offset: usize,
pub len: usize,
}
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());
}
}
#[derive(Copy, Clone)]
pub struct ModuleExport {
pub(crate) module: CompiledModuleId,
pub(crate) entity: EntityIndex,
pub(crate) export_name_index: usize,
}
fn _assert_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Module>();
}
impl crate::runtime::vm::ModuleRuntimeInfo for ModuleInner {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
self.module.module()
}
fn engine_type_index(
&self,
module_index: wasmtime_environ::ModuleInternedTypeIndex,
) -> VMSharedTypeIndex {
self.code
.signatures()
.shared_type(module_index)
.expect("bad module-level interned type index")
}
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 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_array_trampoline(
&self,
signature: VMSharedTypeIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
log::trace!("Looking up trampoline for {signature:?}");
let trampoline_shared_ty = self.engine.signatures().trampoline_type(signature);
let trampoline_module_ty = self
.code
.signatures()
.trampoline_type(trampoline_shared_ty)?;
debug_assert!(self
.engine
.signatures()
.borrow(
self.code
.signatures()
.shared_type(trampoline_module_ty)
.unwrap()
)
.unwrap()
.unwrap_func()
.is_trampoline_type());
let ptr = self
.module
.wasm_to_array_trampoline(trampoline_module_ty)
.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 type_ids(&self) -> &[VMSharedTypeIndex] {
self.code.signatures().as_module_map().values().as_slice()
}
fn offsets(&self) -> &VMOffsets<HostPtr> {
&self.offsets
}
}
impl crate::runtime::vm::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<VMSharedTypeIndex>,
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<VMSharedTypeIndex>,
) -> Self {
BareModuleInfo {
offsets: VMOffsets::new(HostPtr, &module),
module,
one_signature,
}
}
pub(crate) fn into_traitobj(self) -> Arc<dyn crate::runtime::vm::ModuleRuntimeInfo> {
Arc::new(self)
}
}
impl crate::runtime::vm::ModuleRuntimeInfo for BareModuleInfo {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
&self.module
}
fn engine_type_index(
&self,
_module_index: wasmtime_environ::ModuleInternedTypeIndex,
) -> VMSharedTypeIndex {
unreachable!()
}
fn function(&self, _index: DefinedFuncIndex) -> NonNull<VMWasmCallFunction> {
unreachable!()
}
fn array_to_wasm_trampoline(&self, _index: DefinedFuncIndex) -> Option<VMArrayCallFunction> {
unreachable!()
}
fn wasm_to_array_trampoline(
&self,
_signature: VMSharedTypeIndex,
) -> 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 type_ids(&self) -> &[VMSharedTypeIndex] {
match &self.one_signature {
Some(id) => core::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 { .. }));
}
}