use crate::prelude::*;
#[cfg(feature = "std")]
use crate::runtime::vm::open_file_for_mmap;
use crate::runtime::vm::{CompiledModuleId, MmapVec, ModuleMemoryImages, VMWasmCallFunction};
use crate::sync::OnceLock;
use crate::{
Engine,
code::EngineCode,
code_memory::CodeMemory,
instantiate::CompiledModule,
resources::ResourcesRequired,
types::{ExportType, ExternType, ImportType},
};
use alloc::sync::Arc;
use core::fmt;
use core::ops::Range;
use core::ptr::NonNull;
#[cfg(feature = "std")]
use std::{fs::File, path::Path};
use wasmparser::{Parser, ValidPayload, Validator};
#[cfg(feature = "debug")]
use wasmtime_environ::FrameTable;
use wasmtime_environ::{
CompiledFunctionsTable, CompiledModuleInfo, EntityIndex, HostPtr, ModuleTypes, ObjectKind,
TypeTrace, VMOffsets, VMSharedTypeIndex,
};
#[cfg(feature = "gc")]
use wasmtime_unwinder::ExceptionTable;
mod registry;
pub use registry::*;
#[derive(Clone)]
pub struct Module {
inner: Arc<ModuleInner>,
}
struct ModuleInner {
engine: Engine,
module: CompiledModule,
code: Arc<EngineCode>,
memory_images: OnceLock<Option<ModuleMemoryImages>>,
#[cfg(any(feature = "cranelift", feature = "winch"))]
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_binary_or_text(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_binary_or_text_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(binary, None)?
.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 open_file = open_file_for_mmap(file.as_ref())?;
let mmap = crate::runtime::vm::MmapVec::from_file(open_file)?;
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_binary_or_text(&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)
}
pub unsafe fn deserialize_raw(engine: &Engine, memory: NonNull<[u8]>) -> Result<Module> {
let code = unsafe { engine.load_code_raw(memory, 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 file = open_file_for_mmap(path.as_ref())?;
unsafe {
Self::deserialize_open_file(engine, file)
.with_context(|| format!("failed deserialization for: {}", path.as_ref().display()))
}
}
#[cfg(feature = "std")]
pub unsafe fn deserialize_open_file(engine: &Engine, file: File) -> Result<Module> {
let code = engine.load_code_file(file, ObjectKind::Module)?;
Module::from_parts(engine, code, None)
}
pub(crate) fn from_parts(
engine: &Engine,
code_memory: Arc<CodeMemory>,
info_and_types: Option<(CompiledModuleInfo, CompiledFunctionsTable, ModuleTypes)>,
) -> Result<Self> {
let (mut info, index, mut types) = match info_and_types {
Some((info, index, types)) => (info, index, types),
None => postcard::from_bytes(code_memory.wasmtime_info())?,
};
let signatures = engine
.register_and_canonicalize_types(&mut types, core::iter::once(&mut info.module))?;
let code = Arc::new(EngineCode::new(code_memory, signatures, types.into()));
let index = Arc::new(index);
Module::from_parts_raw(engine, code, info, index, true)
}
pub(crate) fn from_parts_raw(
engine: &Engine,
code: Arc<EngineCode>,
info: CompiledModuleInfo,
index: Arc<CompiledFunctionsTable>,
serializable: bool,
) -> Result<Self> {
let module = CompiledModule::from_artifacts(code.clone(), info, index, engine.profiler())?;
let offsets = VMOffsets::new(HostPtr, module.module());
engine
.allocator()
.validate_module(module.module(), &offsets)?;
let _ = serializable;
Ok(Self {
inner: Arc::new(ModuleInner {
engine: engine.clone(),
code,
memory_images: OnceLock::new(),
module,
#[cfg(any(feature = "cranelift", feature = "winch"))]
serializable,
offsets,
}),
})
}
pub fn validate(engine: &Engine, binary: &[u8]) -> Result<()> {
let mut validator = Validator::new_with_features(engine.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"))]
pub fn serialize(&self) -> Result<Vec<u8>> {
if !self.inner.serializable {
bail!("cannot serialize a module exported from a component");
}
Ok(self.engine_code().image().to_vec())
}
pub(crate) fn compiled_module(&self) -> &CompiledModule {
&self.inner.module
}
pub(crate) fn engine_code(&self) -> &Arc<EngineCode> {
&self.inner.code
}
pub(crate) fn env_module(&self) -> &Arc<wasmtime_environ::Module> {
self.compiled_module().module()
}
pub(crate) fn types(&self) -> &ModuleTypes {
self.inner.code.module_types()
}
#[cfg(any(feature = "component-model", feature = "gc-drc"))]
pub(crate) fn signatures(&self) -> &crate::type_registry::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, ty)| {
debug_assert!(ty.is_canonicalized_for_runtime_usage());
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();
let entity = *module.exports.get(name)?;
Some(ModuleExport {
module: self.id(),
entity,
})
}
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.num_defined_memories()).unwrap();
let max_initial_memory_size = em
.memories
.values()
.skip(em.num_imported_memories)
.map(|memory| memory.limits.min)
.max();
let num_tables = u32::try_from(em.num_defined_tables()).unwrap();
let max_initial_table_size = em
.tables
.values()
.skip(em.num_imported_tables)
.map(|table| table.limits.min)
.max();
ResourcesRequired {
num_memories,
max_initial_memory_size,
num_tables,
max_initial_table_size,
}
}
pub fn image_range(&self) -> Range<*const u8> {
self.engine_code().image().as_ptr_range()
}
pub fn initialize_copy_on_write_image(&self) -> Result<()> {
self.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.engine_code().address_map_data())?
.map(|(offset, file_pos)| (offset as usize, file_pos.file_offset())),
)
}
pub fn text(&self) -> &[u8] {
self.engine_code().text()
}
pub fn functions<'a>(&'a self) -> impl ExactSizeIterator<Item = ModuleFunction> + 'a {
let module = self.compiled_module();
self.env_module().defined_func_indices().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(crate) fn offsets(&self) -> &VMOffsets<HostPtr> {
&self.inner.offsets
}
pub(crate) fn wasm_to_array_trampoline(
&self,
signature: VMSharedTypeIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
log::trace!("Looking up trampoline for {signature:?}");
let trampoline_shared_ty = self.inner.engine.signatures().trampoline_type(signature);
let trampoline_module_ty = self
.inner
.code
.signatures()
.trampoline_type(trampoline_shared_ty)?;
debug_assert!(
self.inner
.engine
.signatures()
.borrow(
self.inner
.code
.signatures()
.shared_type(trampoline_module_ty)
.unwrap()
)
.unwrap()
.unwrap_func()
.is_trampoline_type()
);
let ptr = self
.compiled_module()
.wasm_to_array_trampoline(trampoline_module_ty)
.expect("always have a trampoline for the trampoline type")
.as_ptr()
.cast::<VMWasmCallFunction>()
.cast_mut();
Some(NonNull::new(ptr).unwrap())
}
pub(crate) fn memory_images(&self) -> Result<Option<&ModuleMemoryImages>> {
let images = self
.inner
.memory_images
.get_or_try_init(|| memory_images(&self.inner))?
.as_ref();
Ok(images)
}
#[cfg(feature = "gc")]
pub(crate) fn exception_table<'a>(&'a self) -> ExceptionTable<'a> {
ExceptionTable::parse(self.inner.code.exception_tables())
.expect("Exception tables were validated on module load")
}
#[cfg(feature = "debug")]
pub(crate) fn frame_table<'a>(&'a self) -> Option<FrameTable<'a>> {
let data = self.inner.code.frame_tables();
if data.is_empty() {
None
} else {
let orig_text = self.inner.code.text();
Some(
FrameTable::parse(data, orig_text)
.expect("Frame tables were validated on module load"),
)
}
}
#[inline]
pub fn same(a: &Module, b: &Module) -> bool {
Arc::ptr_eq(&a.inner, &b.inner)
}
}
pub struct ModuleFunction {
pub index: wasmtime_environ::FuncIndex,
pub name: Option<String>,
pub offset: usize,
pub len: usize,
}
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,
}
fn _assert_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Module>();
}
fn memory_images(inner: &Arc<ModuleInner>) -> Result<Option<ModuleMemoryImages>> {
if !inner.engine.tunables().memory_init_cow {
return Ok(None);
}
ModuleMemoryImages::new(
&inner.engine,
inner.module.module(),
inner.code.module_memory_image_source(),
)
}
impl crate::vm::ModuleMemoryImageSource for CodeMemory {
fn wasm_data(&self) -> &[u8] {
<Self>::wasm_data(self)
}
fn mmap(&self) -> Option<&MmapVec> {
Some(<Self>::mmap(self))
}
}
#[cfg(test)]
mod tests {
use crate::{CodeBuilder, 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 { .. }));
}
#[test]
#[cfg_attr(miri, ignore)]
fn image_range_is_whole_image() {
let wat = r#"
(module
(memory 1)
(data (i32.const 0) "1234")
(func (export "f") (param i32) (result i32)
local.get 0))
"#;
let engine = Engine::default();
let mut builder = CodeBuilder::new(&engine);
builder.wasm_binary_or_text(wat.as_bytes(), None).unwrap();
let bytes = builder.compile_module_serialized().unwrap();
let module = unsafe { Module::deserialize(&engine, &bytes).unwrap() };
let image_range = module.image_range();
let len = image_range.end.addr() - image_range.start.addr();
assert!(len >= bytes.len());
}
}