use crate::code::CodeObject;
use crate::{
signatures::SignatureCollection,
types::{ExportType, ExternType, ImportType},
Engine,
};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use std::any::Any;
use std::fs;
use std::mem;
use std::ops::Range;
use std::path::Path;
use std::sync::Arc;
use wasmparser::{Parser, ValidPayload, Validator};
use wasmtime_environ::{
DefinedFuncIndex, DefinedMemoryIndex, HostPtr, ModuleEnvironment, ModuleTranslation,
ModuleTypes, ObjectKind, PrimaryMap, VMOffsets, WasmFunctionInfo,
};
use wasmtime_jit::{CodeMemory, CompiledModule, CompiledModuleInfo};
use wasmtime_runtime::{
CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMFunctionBody,
VMSharedSignatureIndex,
};
mod registry;
pub use registry::{is_wasm_trap_pc, register_code, unregister_code, ModuleRegistry};
#[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 Module {
#[cfg(compiler)]
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] 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(compiler)]
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
match Self::new(
engine,
&fs::read(&file).with_context(|| "failed to read input file")?,
) {
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(compiler)]
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] 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(compiler)]
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] 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(compiler)]
pub(crate) fn build_artifacts(
engine: &Engine,
wasm: &[u8],
) -> Result<(MmapVec, Option<(CompiledModuleInfo, ModuleTypes)>)> {
let tunables = &engine.config().tunables;
let compiler = engine.compiler();
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 types = types.finish();
let signatures = translation.exported_signatures.clone();
let (funcs, trampolines) = engine.join_maybe_parallel(
|| Self::compile_functions(engine, &mut translation, &types),
|| -> Result<_> {
engine.run_maybe_parallel(signatures, |sig| {
let ty = &types[sig];
Ok(compiler.compile_host_to_wasm_trampoline(ty)?)
})
},
);
let funcs = funcs?;
let trampolines = trampolines?;
let mut func_infos = PrimaryMap::with_capacity(funcs.len());
let mut compiled_funcs = Vec::with_capacity(funcs.len() + trampolines.len());
for (info, func) in funcs {
let idx = func_infos.push(info);
let sym = format!(
"_wasm_function_{}",
translation.module.func_index(idx).as_u32()
);
compiled_funcs.push((sym, func));
}
for (sig, func) in translation.exported_signatures.iter().zip(trampolines) {
let sym = format!("_trampoline_{}", sig.as_u32());
compiled_funcs.push((sym, func));
}
let mut obj = engine.compiler().object(ObjectKind::Module)?;
let locs = compiler.append_code(&mut obj, &compiled_funcs, tunables, &|i, idx| {
assert!(i < func_infos.len());
let defined = translation.module.defined_func_index(idx).unwrap();
defined.as_u32() as usize
})?;
if tunables.generate_native_debuginfo && !func_infos.is_empty() {
let mut locs = locs.iter();
let mut funcs = compiled_funcs.iter();
let funcs = (0..func_infos.len())
.map(|_| (locs.next().unwrap().0, &*funcs.next().unwrap().1))
.collect();
compiler.append_dwarf(&mut obj, &translation, &funcs)?;
}
let mut locs = locs.into_iter();
let funcs = func_infos
.into_iter()
.map(|(_, info)| (info, locs.next().unwrap().1))
.collect();
let trampolines = translation
.exported_signatures
.iter()
.cloned()
.map(|i| (i, locs.next().unwrap().1))
.collect();
assert!(locs.next().is_none());
engine.append_compiler_info(&mut obj);
engine.append_bti(&mut obj);
let mut obj = wasmtime_jit::ObjectBuilder::new(obj, tunables);
let info = obj.append(translation, funcs, trampolines)?;
obj.serialize_info(&(&info, &types));
let mmap = obj.finish()?;
Ok((mmap, Some((info, types))))
}
#[cfg(compiler)]
pub(crate) fn compile_functions(
engine: &Engine,
translation: &mut ModuleTranslation<'_>,
types: &ModuleTypes,
) -> Result<Vec<(WasmFunctionInfo, Box<dyn Any + Send>)>> {
let tunables = &engine.config().tunables;
let functions = mem::take(&mut translation.function_body_inputs);
let functions = functions.into_iter().collect::<Vec<_>>();
let compiler = engine.compiler();
let funcs = engine.run_maybe_parallel(functions, |(index, func)| {
let offset = func.body.range().start;
let result = compiler.compile_function(&translation, index, func, tunables, types);
result.with_context(|| {
let index = translation.module.func_index(index);
let name = match translation.debuginfo.name_section.func_names.get(&index) {
Some(name) => format!(" (`{}`)", name),
None => String::new(),
};
let index = index.as_u32();
format!("failed to compile wasm function {index}{name} at offset {offset:#x}")
})
})?;
if engine.config().memory_init_cow {
let align = engine.compiler().page_size_align();
let max_always_allowed = engine.config().memory_guaranteed_dense_image_size;
translation.try_static_init(align, max_always_allowed);
}
translation.try_func_table_init();
Ok(funcs)
}
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,
info.trampolines
.iter()
.map(|(idx, f)| (*idx, unsafe { code_memory.vmtrampoline(*f) })),
);
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(), &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(compiler)]
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] 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(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(())
}
}
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(all(feature = "cache", compiler))]
struct HashedEngineCompileEnv<'a>(&'a Engine);
#[cfg(all(feature = "cache", compiler))]
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);
env!("CARGO_PKG_VERSION").hash(hasher);
}
}
impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
self.module.module()
}
fn function(&self, index: DefinedFuncIndex) -> *mut VMFunctionBody {
self.module
.finished_function(index)
.as_ptr()
.cast::<VMFunctionBody>()
.cast_mut()
}
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) -> *mut VMFunctionBody {
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)
}