mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 22:28:21 +00:00
Remove wasmer-{artifact,engine} and merge it into wasmer-compiler and wasmer-vm
This commit is contained in:
@@ -22,6 +22,15 @@ serde_bytes = { version = "0.11", optional = true }
|
||||
smallvec = "1.6"
|
||||
rkyv = { version = "0.7.38", features = ["indexmap"] }
|
||||
|
||||
backtrace = "0.3"
|
||||
rustc-demangle = "0.1"
|
||||
memmap2 = "0.5"
|
||||
more-asserts = "0.2"
|
||||
lazy_static = "1.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
wasmer-vm = { path = "../vm", version = "=2.3.0" }
|
||||
|
||||
[features]
|
||||
default = ["std", "enable-serde" ]
|
||||
# This feature is for compiler implementors, it enables using `Compiler` and
|
||||
|
||||
166
lib/compiler/src/artifact.rs
Normal file
166
lib/compiler/src/artifact.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
//! Generic Artifact abstraction for Wasmer Engines.
|
||||
|
||||
use crate::{CpuFeature, Features};
|
||||
use enumset::EnumSet;
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, mem};
|
||||
use wasmer_types::entity::PrimaryMap;
|
||||
use wasmer_types::{DeserializeError, SerializeError};
|
||||
use wasmer_types::{
|
||||
MemoryIndex, MemoryStyle, ModuleInfo, OwnedDataInitializer, TableIndex, TableStyle,
|
||||
};
|
||||
|
||||
/// An `Artifact` is the product that the `Engine`
|
||||
/// implementation produce and use.
|
||||
///
|
||||
/// The `Artifact` contains the compiled data for a given
|
||||
/// module as well as extra information needed to run the
|
||||
/// module at runtime, such as [`ModuleInfo`] and [`Features`].
|
||||
pub trait ArtifactCreate: Send + Sync + Upcastable {
|
||||
/// Return a reference-counted pointer to the module
|
||||
fn module(&self) -> Arc<ModuleInfo>;
|
||||
|
||||
/// Return a pointer to a module.
|
||||
fn module_ref(&self) -> &ModuleInfo;
|
||||
|
||||
/// Gets a mutable reference to the info.
|
||||
///
|
||||
/// Note: this will return `None` if the module is already instantiated.
|
||||
fn module_mut(&mut self) -> Option<&mut ModuleInfo>;
|
||||
|
||||
/// Returns the features for this Artifact
|
||||
fn features(&self) -> &Features;
|
||||
|
||||
/// Returns the CPU features for this Artifact
|
||||
fn cpu_features(&self) -> EnumSet<CpuFeature>;
|
||||
|
||||
/// Returns the memory styles associated with this `Artifact`.
|
||||
fn memory_styles(&self) -> &PrimaryMap<MemoryIndex, MemoryStyle>;
|
||||
|
||||
/// Returns the table plans associated with this `Artifact`.
|
||||
fn table_styles(&self) -> &PrimaryMap<TableIndex, TableStyle>;
|
||||
|
||||
/// Returns data initializers to pass to `InstanceHandle::initialize`
|
||||
fn data_initializers(&self) -> &[OwnedDataInitializer];
|
||||
|
||||
/// Serializes an artifact into bytes
|
||||
fn serialize(&self) -> Result<Vec<u8>, SerializeError>;
|
||||
|
||||
/// Serializes an artifact into a file path
|
||||
fn serialize_to_file(&self, path: &Path) -> Result<(), SerializeError> {
|
||||
let serialized = self.serialize()?;
|
||||
fs::write(&path, serialized)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 .
|
||||
/// Trait needed to get downcasting of `Engine`s to work.
|
||||
pub trait Upcastable {
|
||||
/// upcast ref
|
||||
fn upcast_any_ref(&'_ self) -> &'_ dyn Any;
|
||||
/// upcast mut ref
|
||||
fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any;
|
||||
/// upcast boxed dyn
|
||||
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
|
||||
}
|
||||
|
||||
impl<T: Any + Send + Sync + 'static> Upcastable for T {
|
||||
#[inline]
|
||||
fn upcast_any_ref(&'_ self) -> &'_ dyn Any {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn ArtifactCreate + 'static {
|
||||
/// Try to downcast the artifact into a given type.
|
||||
#[inline]
|
||||
pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> {
|
||||
self.upcast_any_ref().downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// Try to downcast the artifact into a given type mutably.
|
||||
#[inline]
|
||||
pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> {
|
||||
self.upcast_any_mut().downcast_mut::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata header which holds an ABI version and the length of the remaining
|
||||
/// metadata.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MetadataHeader {
|
||||
magic: [u8; 8],
|
||||
version: u32,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
impl MetadataHeader {
|
||||
/// Current ABI version. Increment this any time breaking changes are made
|
||||
/// to the format of the serialized data.
|
||||
const CURRENT_VERSION: u32 = 1;
|
||||
|
||||
/// Magic number to identify wasmer metadata.
|
||||
const MAGIC: [u8; 8] = *b"WASMER\0\0";
|
||||
|
||||
/// Length of the metadata header.
|
||||
pub const LEN: usize = 16;
|
||||
|
||||
/// Alignment of the metadata.
|
||||
pub const ALIGN: usize = 16;
|
||||
|
||||
/// Creates a new header for metadata of the given length.
|
||||
pub fn new(len: usize) -> Self {
|
||||
Self {
|
||||
magic: Self::MAGIC,
|
||||
version: Self::CURRENT_VERSION,
|
||||
len: len.try_into().expect("metadata exceeds maximum length"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the header into its bytes representation.
|
||||
pub fn into_bytes(self) -> [u8; 16] {
|
||||
unsafe { mem::transmute(self) }
|
||||
}
|
||||
|
||||
/// Parses the header and returns the length of the metadata following it.
|
||||
pub fn parse(bytes: &[u8]) -> Result<usize, DeserializeError> {
|
||||
if bytes.as_ptr() as usize % 16 != 0 {
|
||||
return Err(DeserializeError::CorruptedBinary(
|
||||
"misaligned metadata".to_string(),
|
||||
));
|
||||
}
|
||||
let bytes: [u8; 16] = bytes
|
||||
.get(..16)
|
||||
.ok_or_else(|| {
|
||||
DeserializeError::CorruptedBinary("invalid metadata header".to_string())
|
||||
})?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let header: Self = unsafe { mem::transmute(bytes) };
|
||||
if header.magic != Self::MAGIC {
|
||||
return Err(DeserializeError::Incompatible(
|
||||
"The provided bytes were not serialized by Wasmer".to_string(),
|
||||
));
|
||||
}
|
||||
if header.version != Self::CURRENT_VERSION {
|
||||
return Err(DeserializeError::Incompatible(
|
||||
"The provided bytes were serialized by an incompatible version of Wasmer"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(header.len as usize)
|
||||
}
|
||||
}
|
||||
162
lib/compiler/src/engine/artifact.rs
Normal file
162
lib/compiler/src/engine/artifact.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use crate::CpuFeature;
|
||||
use crate::{resolve_imports, Export, InstantiationError, RuntimeError, Tunables};
|
||||
use crate::{ArtifactCreate, Upcastable};
|
||||
use std::any::Any;
|
||||
use wasmer_types::entity::BoxedSlice;
|
||||
use wasmer_types::{DataInitializer, FunctionIndex, LocalFunctionIndex, SignatureIndex};
|
||||
use wasmer_vm::{
|
||||
FuncDataRegistry, FunctionBodyPtr, InstanceAllocator, InstanceHandle, TrapHandler,
|
||||
VMSharedSignatureIndex, VMTrampoline,
|
||||
};
|
||||
|
||||
/// An `Artifact` is the product that the `Engine`
|
||||
/// implementation produce and use.
|
||||
///
|
||||
|
||||
/// An `Artifact` is the product that the `Engine`
|
||||
/// implementation produce and use.
|
||||
///
|
||||
/// The `ArtifactRun` contains the extra information needed to run the
|
||||
/// module at runtime, such as [`ModuleInfo`] and [`Features`].
|
||||
pub trait Artifact: Send + Sync + Upcastable + ArtifactCreate {
|
||||
/// Register thie `Artifact` stack frame information into the global scope.
|
||||
///
|
||||
/// This is required to ensure that any traps can be properly symbolicated.
|
||||
fn register_frame_info(&self);
|
||||
|
||||
/// Returns the functions allocated in memory or this `Artifact`
|
||||
/// ready to be run.
|
||||
fn finished_functions(&self) -> &BoxedSlice<LocalFunctionIndex, FunctionBodyPtr>;
|
||||
|
||||
/// Returns the function call trampolines allocated in memory of this
|
||||
/// `Artifact`, ready to be run.
|
||||
fn finished_function_call_trampolines(&self) -> &BoxedSlice<SignatureIndex, VMTrampoline>;
|
||||
|
||||
/// Returns the dynamic function trampolines allocated in memory
|
||||
/// of this `Artifact`, ready to be run.
|
||||
fn finished_dynamic_function_trampolines(&self) -> &BoxedSlice<FunctionIndex, FunctionBodyPtr>;
|
||||
|
||||
/// Returns the associated VM signatures for this `Artifact`.
|
||||
fn signatures(&self) -> &BoxedSlice<SignatureIndex, VMSharedSignatureIndex>;
|
||||
|
||||
/// Get the func data registry
|
||||
fn func_data_registry(&self) -> &FuncDataRegistry;
|
||||
|
||||
/// Do preinstantiation logic that is executed before instantiating
|
||||
fn preinstantiate(&self) -> Result<(), InstantiationError> {
|
||||
Ok(())
|
||||
}
|
||||
/// Crate an `Instance` from this `Artifact`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See [`InstanceHandle::new`].
|
||||
unsafe fn instantiate(
|
||||
&self,
|
||||
tunables: &dyn Tunables,
|
||||
imports: &[Export],
|
||||
host_state: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle, InstantiationError> {
|
||||
// Validate the CPU features this module was compiled with against the
|
||||
// host CPU features.
|
||||
let host_cpu_features = CpuFeature::for_host();
|
||||
if !host_cpu_features.is_superset(self.cpu_features()) {
|
||||
return Err(InstantiationError::CpuFeature(format!(
|
||||
"{:?}",
|
||||
self.cpu_features().difference(host_cpu_features)
|
||||
)));
|
||||
}
|
||||
|
||||
self.preinstantiate()?;
|
||||
|
||||
let module = self.module();
|
||||
let (imports, import_function_envs) = {
|
||||
let mut imports = resolve_imports(
|
||||
&module,
|
||||
imports,
|
||||
self.finished_dynamic_function_trampolines(),
|
||||
self.memory_styles(),
|
||||
self.table_styles(),
|
||||
)
|
||||
.map_err(InstantiationError::Link)?;
|
||||
|
||||
// Get the `WasmerEnv::init_with_instance` function pointers and the pointers
|
||||
// to the envs to call it on.
|
||||
let import_function_envs = imports.get_imported_function_envs();
|
||||
|
||||
(imports, import_function_envs)
|
||||
};
|
||||
|
||||
// Get pointers to where metadata about local memories should live in VM memory.
|
||||
// Get pointers to where metadata about local tables should live in VM memory.
|
||||
|
||||
let (allocator, memory_definition_locations, table_definition_locations) =
|
||||
InstanceAllocator::new(&*module);
|
||||
let finished_memories = tunables
|
||||
.create_memories(&module, self.memory_styles(), &memory_definition_locations)
|
||||
.map_err(InstantiationError::Link)?
|
||||
.into_boxed_slice();
|
||||
let finished_tables = tunables
|
||||
.create_tables(&module, self.table_styles(), &table_definition_locations)
|
||||
.map_err(InstantiationError::Link)?
|
||||
.into_boxed_slice();
|
||||
let finished_globals = tunables
|
||||
.create_globals(&module)
|
||||
.map_err(InstantiationError::Link)?
|
||||
.into_boxed_slice();
|
||||
|
||||
self.register_frame_info();
|
||||
|
||||
let handle = InstanceHandle::new(
|
||||
allocator,
|
||||
module,
|
||||
self.finished_functions().clone(),
|
||||
self.finished_function_call_trampolines().clone(),
|
||||
finished_memories,
|
||||
finished_tables,
|
||||
finished_globals,
|
||||
imports,
|
||||
self.signatures().clone(),
|
||||
host_state,
|
||||
import_function_envs,
|
||||
)
|
||||
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))?;
|
||||
Ok(handle)
|
||||
}
|
||||
/// Finishes the instantiation of a just created `InstanceHandle`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See [`InstanceHandle::finish_instantiation`].
|
||||
unsafe fn finish_instantiation(
|
||||
&self,
|
||||
trap_handler: &(dyn TrapHandler + 'static),
|
||||
handle: &InstanceHandle,
|
||||
) -> Result<(), InstantiationError> {
|
||||
let data_initializers = self
|
||||
.data_initializers()
|
||||
.iter()
|
||||
.map(|init| DataInitializer {
|
||||
location: init.location.clone(),
|
||||
data: &*init.data,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
handle
|
||||
.finish_instantiation(trap_handler, &data_initializers)
|
||||
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Artifact + 'static {
|
||||
/// Try to downcast the artifact into a given type.
|
||||
#[inline]
|
||||
pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> {
|
||||
self.upcast_any_ref().downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// Try to downcast the artifact into a given type mutably.
|
||||
#[inline]
|
||||
pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> {
|
||||
self.upcast_any_mut().downcast_mut::<T>()
|
||||
}
|
||||
}
|
||||
49
lib/compiler/src/engine/error.rs
Normal file
49
lib/compiler/src/engine/error.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
//! The WebAssembly possible errors
|
||||
use crate::engine::trap::RuntimeError;
|
||||
use thiserror::Error;
|
||||
pub use wasmer_types::{DeserializeError, ImportError, SerializeError};
|
||||
|
||||
/// The WebAssembly.LinkError object indicates an error during
|
||||
/// module instantiation (besides traps from the start function).
|
||||
///
|
||||
/// This is based on the [link error][link-error] API.
|
||||
///
|
||||
/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Link error: {0}")]
|
||||
pub enum LinkError {
|
||||
/// An error occurred when checking the import types.
|
||||
#[error("Error while importing {0:?}.{1:?}: {2}")]
|
||||
Import(String, String, ImportError),
|
||||
|
||||
/// A trap ocurred during linking.
|
||||
#[error("RuntimeError occurred during linking: {0}")]
|
||||
Trap(#[source] RuntimeError),
|
||||
|
||||
/// Insufficient resources available for linking.
|
||||
#[error("Insufficient resources: {0}")]
|
||||
Resource(String),
|
||||
}
|
||||
|
||||
/// An error while instantiating a module.
|
||||
///
|
||||
/// This is not a common WebAssembly error, however
|
||||
/// we need to differentiate from a `LinkError` (an error
|
||||
/// that happens while linking, on instantiation) and a
|
||||
/// Trap that occurs when calling the WebAssembly module
|
||||
/// start function.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InstantiationError {
|
||||
/// A linking ocurred during instantiation.
|
||||
#[error(transparent)]
|
||||
Link(LinkError),
|
||||
|
||||
/// The module was compiled with a CPU feature that is not available on
|
||||
/// the current host.
|
||||
#[error("module compiled with CPU feature that is missing from host")]
|
||||
CpuFeature(String),
|
||||
|
||||
/// A runtime error occured while invoking the start function
|
||||
#[error(transparent)]
|
||||
Start(RuntimeError),
|
||||
}
|
||||
166
lib/compiler/src/engine/export.rs
Normal file
166
lib/compiler/src/engine/export.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use std::sync::Arc;
|
||||
use wasmer_vm::{ImportInitializerFuncPtr, VMExtern, VMFunction, VMGlobal, VMMemory, VMTable};
|
||||
|
||||
/// The value of an export passed from one instance to another.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Export {
|
||||
/// A function export value.
|
||||
Function(ExportFunction),
|
||||
|
||||
/// A table export value.
|
||||
Table(VMTable),
|
||||
|
||||
/// A memory export value.
|
||||
Memory(VMMemory),
|
||||
|
||||
/// A global export value.
|
||||
Global(VMGlobal),
|
||||
}
|
||||
|
||||
impl From<Export> for VMExtern {
|
||||
fn from(other: Export) -> Self {
|
||||
match other {
|
||||
Export::Function(ExportFunction { vm_function, .. }) => Self::Function(vm_function),
|
||||
Export::Memory(vm_memory) => Self::Memory(vm_memory),
|
||||
Export::Table(vm_table) => Self::Table(vm_table),
|
||||
Export::Global(vm_global) => Self::Global(vm_global),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMExtern> for Export {
|
||||
fn from(other: VMExtern) -> Self {
|
||||
match other {
|
||||
VMExtern::Function(vm_function) => Self::Function(ExportFunction {
|
||||
vm_function,
|
||||
metadata: None,
|
||||
}),
|
||||
VMExtern::Memory(vm_memory) => Self::Memory(vm_memory),
|
||||
VMExtern::Table(vm_table) => Self::Table(vm_table),
|
||||
VMExtern::Global(vm_global) => Self::Global(vm_global),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra metadata about `ExportFunction`s.
|
||||
///
|
||||
/// The metadata acts as a kind of manual virtual dispatch. We store the
|
||||
/// user-supplied `WasmerEnv` as a void pointer and have methods on it
|
||||
/// that have been adapted to accept a void pointer.
|
||||
///
|
||||
/// This struct owns the original `host_env`, thus when it gets dropped
|
||||
/// it calls the `drop` function on it.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ExportFunctionMetadata {
|
||||
/// This field is stored here to be accessible by `Drop`.
|
||||
///
|
||||
/// At the time it was added, it's not accessed anywhere outside of
|
||||
/// the `Drop` implementation. This field is the "master copy" of the env,
|
||||
/// that is, the original env passed in by the user. Every time we create
|
||||
/// an `Instance` we clone this with the `host_env_clone_fn` field.
|
||||
///
|
||||
/// Thus, we only bother to store the master copy at all here so that
|
||||
/// we can free it.
|
||||
///
|
||||
/// See `wasmer_vm::export::VMFunction::vmctx` for the version of
|
||||
/// this pointer that is used by the VM when creating an `Instance`.
|
||||
pub(crate) host_env: *mut std::ffi::c_void,
|
||||
|
||||
/// Function pointer to `WasmerEnv::init_with_instance(&mut self, instance: &Instance)`.
|
||||
///
|
||||
/// This function is called to finish setting up the environment after
|
||||
/// we create the `api::Instance`.
|
||||
// This one is optional for now because dynamic host envs need the rest
|
||||
// of this without the init fn
|
||||
pub(crate) import_init_function_ptr: Option<ImportInitializerFuncPtr>,
|
||||
|
||||
/// A function analogous to `Clone::clone` that returns a leaked `Box`.
|
||||
pub(crate) host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void,
|
||||
|
||||
/// The destructor to free the host environment.
|
||||
///
|
||||
/// # Safety
|
||||
/// - This function should only be called in when properly synchronized.
|
||||
/// For example, in the `Drop` implementation of this type.
|
||||
pub(crate) host_env_drop_fn: unsafe fn(*mut std::ffi::c_void),
|
||||
}
|
||||
|
||||
/// This can be `Send` because `host_env` comes from `WasmerEnv` which is
|
||||
/// `Send`. Therefore all operations should work on any thread.
|
||||
unsafe impl Send for ExportFunctionMetadata {}
|
||||
/// This data may be shared across threads, `drop` is an unsafe function
|
||||
/// pointer, so care must be taken when calling it.
|
||||
unsafe impl Sync for ExportFunctionMetadata {}
|
||||
|
||||
impl ExportFunctionMetadata {
|
||||
/// Create an `ExportFunctionMetadata` type with information about
|
||||
/// the exported function.
|
||||
///
|
||||
/// # Safety
|
||||
/// - the `host_env` must be `Send`.
|
||||
/// - all function pointers must work on any thread.
|
||||
pub unsafe fn new(
|
||||
host_env: *mut std::ffi::c_void,
|
||||
import_init_function_ptr: Option<ImportInitializerFuncPtr>,
|
||||
host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void,
|
||||
host_env_drop_fn: fn(*mut std::ffi::c_void),
|
||||
) -> Self {
|
||||
Self {
|
||||
host_env,
|
||||
import_init_function_ptr,
|
||||
host_env_clone_fn,
|
||||
host_env_drop_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have to free `host_env` here because we always clone it before using it
|
||||
// so all the `host_env`s freed at the `Instance` level won't touch the original.
|
||||
impl Drop for ExportFunctionMetadata {
|
||||
fn drop(&mut self) {
|
||||
if !self.host_env.is_null() {
|
||||
// # Safety
|
||||
// - This is correct because we know no other references
|
||||
// to this data can exist if we're dropping it.
|
||||
unsafe {
|
||||
(self.host_env_drop_fn)(self.host_env);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A function export value with an extra function pointer to initialize
|
||||
/// host environments.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExportFunction {
|
||||
/// The VM function, containing most of the data.
|
||||
pub vm_function: VMFunction,
|
||||
/// Contains functions necessary to create and initialize host envs
|
||||
/// with each `Instance` as well as being responsible for the
|
||||
/// underlying memory of the host env.
|
||||
pub metadata: Option<Arc<ExportFunctionMetadata>>,
|
||||
}
|
||||
|
||||
impl From<ExportFunction> for Export {
|
||||
fn from(func: ExportFunction) -> Self {
|
||||
Self::Function(func)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMTable> for Export {
|
||||
fn from(table: VMTable) -> Self {
|
||||
Self::Table(table)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMMemory> for Export {
|
||||
fn from(memory: VMMemory) -> Self {
|
||||
Self::Memory(memory)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMGlobal> for Export {
|
||||
fn from(global: VMGlobal) -> Self {
|
||||
Self::Global(global)
|
||||
}
|
||||
}
|
||||
101
lib/compiler/src/engine/inner.rs
Normal file
101
lib/compiler/src/engine/inner.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
//! Engine trait and associated types.
|
||||
|
||||
use crate::engine::tunables::Tunables;
|
||||
use crate::Artifact;
|
||||
use crate::Target;
|
||||
use memmap2::Mmap;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||
use std::sync::Arc;
|
||||
use wasmer_types::{CompileError, DeserializeError, FunctionType};
|
||||
use wasmer_vm::{VMCallerCheckedAnyfunc, VMFuncRef, VMSharedSignatureIndex};
|
||||
|
||||
/// A unimplemented Wasmer `Engine`.
|
||||
///
|
||||
/// This trait is used by implementors to implement custom engines
|
||||
/// such as: Universal or Native.
|
||||
///
|
||||
/// The product that an `Engine` produces and consumes is the [`Artifact`].
|
||||
pub trait Engine {
|
||||
/// Gets the target
|
||||
fn target(&self) -> &Target;
|
||||
|
||||
/// Register a signature
|
||||
fn register_signature(&self, func_type: &FunctionType) -> VMSharedSignatureIndex;
|
||||
|
||||
/// Register a function's data.
|
||||
fn register_function_metadata(&self, func_data: VMCallerCheckedAnyfunc) -> VMFuncRef;
|
||||
|
||||
/// Lookup a signature
|
||||
fn lookup_signature(&self, sig: VMSharedSignatureIndex) -> Option<FunctionType>;
|
||||
|
||||
/// Validates a WebAssembly module
|
||||
fn validate(&self, binary: &[u8]) -> Result<(), CompileError>;
|
||||
|
||||
/// Compile a WebAssembly binary
|
||||
fn compile(
|
||||
&self,
|
||||
binary: &[u8],
|
||||
tunables: &dyn Tunables,
|
||||
) -> Result<Arc<dyn Artifact>, CompileError>;
|
||||
|
||||
/// Deserializes a WebAssembly module
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The serialized content must represent a serialized WebAssembly module.
|
||||
unsafe fn deserialize(&self, bytes: &[u8]) -> Result<Arc<dyn Artifact>, DeserializeError>;
|
||||
|
||||
/// Deserializes a WebAssembly module from a path
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The file's content must represent a serialized WebAssembly module.
|
||||
unsafe fn deserialize_from_file(
|
||||
&self,
|
||||
file_ref: &Path,
|
||||
) -> Result<Arc<dyn Artifact>, DeserializeError> {
|
||||
let file = std::fs::File::open(file_ref)?;
|
||||
let mmap = Mmap::map(&file)?;
|
||||
self.deserialize(&mmap)
|
||||
}
|
||||
|
||||
/// A unique identifier for this object.
|
||||
///
|
||||
/// This exists to allow us to compare two Engines for equality. Otherwise,
|
||||
/// comparing two trait objects unsafely relies on implementation details
|
||||
/// of trait representation.
|
||||
fn id(&self) -> &EngineId;
|
||||
|
||||
/// Clone the engine
|
||||
fn cloned(&self) -> Arc<dyn Engine + Send + Sync>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
/// A unique identifier for an Engine.
|
||||
pub struct EngineId {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl EngineId {
|
||||
/// Format this identifier as a string.
|
||||
pub fn id(&self) -> String {
|
||||
format!("{}", &self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for EngineId {
|
||||
fn clone(&self) -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EngineId {
|
||||
fn default() -> Self {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
Self {
|
||||
id: NEXT_ID.fetch_add(1, SeqCst),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/compiler/src/engine/mod.rs
Normal file
17
lib/compiler/src/engine/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Generic Engine abstraction for Wasmer Engines.
|
||||
|
||||
mod artifact;
|
||||
mod error;
|
||||
mod export;
|
||||
mod inner;
|
||||
mod resolver;
|
||||
mod trap;
|
||||
mod tunables;
|
||||
|
||||
pub use self::artifact::Artifact;
|
||||
pub use self::error::{InstantiationError, LinkError};
|
||||
pub use self::export::{Export, ExportFunction, ExportFunctionMetadata};
|
||||
pub use self::inner::{Engine, EngineId};
|
||||
pub use self::resolver::resolve_imports;
|
||||
pub use self::trap::*;
|
||||
pub use self::tunables::Tunables;
|
||||
218
lib/compiler/src/engine/resolver.rs
Normal file
218
lib/compiler/src/engine/resolver.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
//! Custom resolution for external references.
|
||||
|
||||
use crate::{Export, ExportFunctionMetadata, LinkError};
|
||||
use more_asserts::assert_ge;
|
||||
use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap};
|
||||
use wasmer_types::{
|
||||
ExternType, FunctionIndex, ImportError, ImportIndex, MemoryIndex, ModuleInfo, TableIndex,
|
||||
};
|
||||
|
||||
use wasmer_vm::{
|
||||
FunctionBodyPtr, ImportFunctionEnv, Imports, MemoryStyle, TableStyle, VMFunctionBody,
|
||||
VMFunctionEnvironment, VMFunctionImport, VMFunctionKind, VMGlobalImport, VMMemoryImport,
|
||||
VMTableImport,
|
||||
};
|
||||
|
||||
/// Get an `ExternType` given a import index.
|
||||
fn get_extern_from_import(module: &ModuleInfo, import_index: &ImportIndex) -> ExternType {
|
||||
match import_index {
|
||||
ImportIndex::Function(index) => {
|
||||
let func = module.signatures[module.functions[*index]].clone();
|
||||
ExternType::Function(func)
|
||||
}
|
||||
ImportIndex::Table(index) => {
|
||||
let table = module.tables[*index];
|
||||
ExternType::Table(table)
|
||||
}
|
||||
ImportIndex::Memory(index) => {
|
||||
let memory = module.memories[*index];
|
||||
ExternType::Memory(memory)
|
||||
}
|
||||
ImportIndex::Global(index) => {
|
||||
let global = module.globals[*index];
|
||||
ExternType::Global(global)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an `ExternType` given an export (and Engine signatures in case is a function).
|
||||
fn get_extern_from_export(_module: &ModuleInfo, export: &Export) -> ExternType {
|
||||
match export {
|
||||
Export::Function(ref f) => ExternType::Function(f.vm_function.signature.clone()),
|
||||
Export::Table(ref t) => ExternType::Table(*t.ty()),
|
||||
Export::Memory(ref m) => ExternType::Memory(m.ty()),
|
||||
Export::Global(ref g) => {
|
||||
let global = g.from.ty();
|
||||
ExternType::Global(*global)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function allows to match all imports of a `ModuleInfo` with concrete definitions provided by
|
||||
/// a `Resolver`.
|
||||
///
|
||||
/// If all imports are satisfied returns an `Imports` instance required for a module instantiation.
|
||||
pub fn resolve_imports(
|
||||
module: &ModuleInfo,
|
||||
imports: &[Export],
|
||||
finished_dynamic_function_trampolines: &BoxedSlice<FunctionIndex, FunctionBodyPtr>,
|
||||
memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
|
||||
_table_styles: &PrimaryMap<TableIndex, TableStyle>,
|
||||
) -> Result<Imports, LinkError> {
|
||||
let mut function_imports = PrimaryMap::with_capacity(module.num_imported_functions);
|
||||
let mut host_function_env_initializers =
|
||||
PrimaryMap::with_capacity(module.num_imported_functions);
|
||||
let mut table_imports = PrimaryMap::with_capacity(module.num_imported_tables);
|
||||
let mut memory_imports = PrimaryMap::with_capacity(module.num_imported_memories);
|
||||
let mut global_imports = PrimaryMap::with_capacity(module.num_imported_globals);
|
||||
|
||||
for ((module_name, field, import_idx), import_index) in module.imports.iter() {
|
||||
let import_extern = get_extern_from_import(module, import_index);
|
||||
let resolved = if let Some(r) = imports.get(*import_idx as usize) {
|
||||
r
|
||||
} else {
|
||||
return Err(LinkError::Import(
|
||||
module_name.to_string(),
|
||||
field.to_string(),
|
||||
ImportError::UnknownImport(import_extern),
|
||||
));
|
||||
};
|
||||
let export_extern = get_extern_from_export(module, resolved);
|
||||
if !export_extern.is_compatible_with(&import_extern) {
|
||||
return Err(LinkError::Import(
|
||||
module_name.to_string(),
|
||||
field.to_string(),
|
||||
ImportError::IncompatibleType(import_extern, export_extern),
|
||||
));
|
||||
}
|
||||
match resolved {
|
||||
Export::Function(ref f) => {
|
||||
let address = match f.vm_function.kind {
|
||||
VMFunctionKind::Dynamic => {
|
||||
// If this is a dynamic imported function,
|
||||
// the address of the function is the address of the
|
||||
// reverse trampoline.
|
||||
let index = FunctionIndex::new(function_imports.len());
|
||||
finished_dynamic_function_trampolines[index].0 as *mut VMFunctionBody as _
|
||||
|
||||
// TODO: We should check that the f.vmctx actually matches
|
||||
// the shape of `VMDynamicFunctionImportContext`
|
||||
}
|
||||
VMFunctionKind::Static => f.vm_function.address,
|
||||
};
|
||||
|
||||
// Clone the host env for this `Instance`.
|
||||
let env = if let Some(ExportFunctionMetadata {
|
||||
host_env_clone_fn: clone,
|
||||
..
|
||||
}) = f.metadata.as_deref()
|
||||
{
|
||||
// TODO: maybe start adding asserts in all these
|
||||
// unsafe blocks to prevent future changes from
|
||||
// horribly breaking things.
|
||||
unsafe {
|
||||
assert!(!f.vm_function.vmctx.host_env.is_null());
|
||||
(clone)(f.vm_function.vmctx.host_env)
|
||||
}
|
||||
} else {
|
||||
// No `clone` function means we're dealing with some
|
||||
// other kind of `vmctx`, not a host env of any
|
||||
// kind.
|
||||
unsafe { f.vm_function.vmctx.host_env }
|
||||
};
|
||||
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: address,
|
||||
environment: VMFunctionEnvironment { host_env: env },
|
||||
});
|
||||
|
||||
let initializer = f.metadata.as_ref().and_then(|m| m.import_init_function_ptr);
|
||||
let clone = f.metadata.as_ref().map(|m| m.host_env_clone_fn);
|
||||
let destructor = f.metadata.as_ref().map(|m| m.host_env_drop_fn);
|
||||
let import_function_env =
|
||||
if let (Some(clone), Some(destructor)) = (clone, destructor) {
|
||||
ImportFunctionEnv::Env {
|
||||
env,
|
||||
clone,
|
||||
initializer,
|
||||
destructor,
|
||||
}
|
||||
} else {
|
||||
ImportFunctionEnv::NoEnv
|
||||
};
|
||||
|
||||
host_function_env_initializers.push(import_function_env);
|
||||
}
|
||||
Export::Table(ref t) => match import_index {
|
||||
ImportIndex::Table(index) => {
|
||||
let import_table_ty = t.from.ty();
|
||||
let expected_table_ty = &module.tables[*index];
|
||||
if import_table_ty.ty != expected_table_ty.ty {
|
||||
return Err(LinkError::Import(
|
||||
module_name.to_string(),
|
||||
field.to_string(),
|
||||
ImportError::IncompatibleType(import_extern, export_extern),
|
||||
));
|
||||
}
|
||||
|
||||
table_imports.push(VMTableImport {
|
||||
definition: t.from.vmtable(),
|
||||
from: t.from.clone(),
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Table resolution did not match");
|
||||
}
|
||||
},
|
||||
Export::Memory(ref m) => {
|
||||
match import_index {
|
||||
ImportIndex::Memory(index) => {
|
||||
// Sanity-check: Ensure that the imported memory has at least
|
||||
// guard-page protections the importing module expects it to have.
|
||||
let export_memory_style = m.style();
|
||||
let import_memory_style = &memory_styles[*index];
|
||||
if let (
|
||||
MemoryStyle::Static { bound, .. },
|
||||
MemoryStyle::Static {
|
||||
bound: import_bound,
|
||||
..
|
||||
},
|
||||
) = (export_memory_style.clone(), &import_memory_style)
|
||||
{
|
||||
assert_ge!(bound, *import_bound);
|
||||
}
|
||||
assert_ge!(
|
||||
export_memory_style.offset_guard_size(),
|
||||
import_memory_style.offset_guard_size()
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
// This should never be reached, as we did compatibility
|
||||
// checks before
|
||||
panic!("Memory resolution didn't matched");
|
||||
}
|
||||
}
|
||||
|
||||
memory_imports.push(VMMemoryImport {
|
||||
definition: m.from.vmmemory(),
|
||||
from: m.from.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Export::Global(ref g) => {
|
||||
global_imports.push(VMGlobalImport {
|
||||
definition: g.from.vmglobal(),
|
||||
from: g.from.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Imports::new(
|
||||
function_imports,
|
||||
host_function_env_initializers,
|
||||
table_imports,
|
||||
memory_imports,
|
||||
global_imports,
|
||||
))
|
||||
}
|
||||
275
lib/compiler/src/engine/trap/error.rs
Normal file
275
lib/compiler/src/engine/trap/error.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO};
|
||||
use backtrace::Backtrace;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmer_vm::{raise_user_trap, Trap, TrapCode};
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeError {
|
||||
inner: Arc<RuntimeErrorInner>,
|
||||
}
|
||||
|
||||
/// The source of the `RuntimeError`.
|
||||
#[derive(Debug)]
|
||||
enum RuntimeErrorSource {
|
||||
Generic(String),
|
||||
OutOfMemory,
|
||||
User(Box<dyn Error + Send + Sync>),
|
||||
Trap(TrapCode),
|
||||
}
|
||||
|
||||
impl fmt::Display for RuntimeErrorSource {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Generic(s) => write!(f, "{}", s),
|
||||
Self::User(s) => write!(f, "{}", s),
|
||||
Self::OutOfMemory => write!(f, "Wasmer VM out of memory"),
|
||||
Self::Trap(s) => write!(f, "{}", s.message()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RuntimeErrorInner {
|
||||
/// The source error (this can be a custom user `Error` or a [`TrapCode`])
|
||||
source: RuntimeErrorSource,
|
||||
/// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
/// The native backtrace
|
||||
native_trace: Backtrace,
|
||||
}
|
||||
|
||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
(t, t)
|
||||
}
|
||||
|
||||
impl RuntimeError {
|
||||
/// Creates a new generic `RuntimeError` with the given `message`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let trap = wasmer_engine::RuntimeError::new("unexpected error");
|
||||
/// assert_eq!("unexpected error", trap.message());
|
||||
/// ```
|
||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||
let info = FRAME_INFO.read().unwrap();
|
||||
let msg = message.into();
|
||||
Self::new_with_trace(
|
||||
&info,
|
||||
None,
|
||||
RuntimeErrorSource::Generic(msg),
|
||||
Backtrace::new_unresolved(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new RuntimeError from a Trap.
|
||||
pub fn from_trap(trap: Trap) -> Self {
|
||||
let info = FRAME_INFO.read().unwrap();
|
||||
match trap {
|
||||
// A user error
|
||||
Trap::User(error) => {
|
||||
match error.downcast::<Self>() {
|
||||
// The error is already a RuntimeError, we return it directly
|
||||
Ok(runtime_error) => *runtime_error,
|
||||
Err(e) => Self::new_with_trace(
|
||||
&info,
|
||||
None,
|
||||
RuntimeErrorSource::User(e),
|
||||
Backtrace::new_unresolved(),
|
||||
),
|
||||
}
|
||||
}
|
||||
// A trap caused by the VM being Out of Memory
|
||||
Trap::OOM { backtrace } => {
|
||||
Self::new_with_trace(&info, None, RuntimeErrorSource::OutOfMemory, backtrace)
|
||||
}
|
||||
// A trap caused by an error on the generated machine code for a Wasm function
|
||||
Trap::Wasm {
|
||||
pc,
|
||||
signal_trap,
|
||||
backtrace,
|
||||
} => {
|
||||
let code = info
|
||||
.lookup_trap_info(pc)
|
||||
.map_or(signal_trap.unwrap_or(TrapCode::StackOverflow), |info| {
|
||||
info.trap_code
|
||||
});
|
||||
Self::new_with_trace(&info, Some(pc), RuntimeErrorSource::Trap(code), backtrace)
|
||||
}
|
||||
// A trap triggered manually from the Wasmer runtime
|
||||
Trap::Lib {
|
||||
trap_code,
|
||||
backtrace,
|
||||
} => Self::new_with_trace(&info, None, RuntimeErrorSource::Trap(trap_code), backtrace),
|
||||
}
|
||||
}
|
||||
|
||||
/// Raises a custom user Error
|
||||
#[deprecated(since = "2.1.1", note = "return a Result from host functions instead")]
|
||||
pub fn raise(error: Box<dyn Error + Send + Sync>) -> ! {
|
||||
unsafe { raise_user_trap(error) }
|
||||
}
|
||||
|
||||
/// Creates a custom user Error.
|
||||
///
|
||||
/// This error object can be passed through Wasm frames and later retrieved
|
||||
/// using the `downcast` method.
|
||||
pub fn user(error: Box<dyn Error + Send + Sync>) -> Self {
|
||||
match error.downcast::<Self>() {
|
||||
// The error is already a RuntimeError, we return it directly
|
||||
Ok(runtime_error) => *runtime_error,
|
||||
Err(error) => {
|
||||
let info = FRAME_INFO.read().unwrap();
|
||||
Self::new_with_trace(
|
||||
&info,
|
||||
None,
|
||||
RuntimeErrorSource::User(error),
|
||||
Backtrace::new_unresolved(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_trace(
|
||||
info: &GlobalFrameInfo,
|
||||
trap_pc: Option<usize>,
|
||||
source: RuntimeErrorSource,
|
||||
native_trace: Backtrace,
|
||||
) -> Self {
|
||||
// Let's construct the trace
|
||||
let wasm_trace = native_trace
|
||||
.frames()
|
||||
.iter()
|
||||
.filter_map(|frame| {
|
||||
let pc = frame.ip() as usize;
|
||||
if pc == 0 {
|
||||
None
|
||||
} else {
|
||||
// Note that we need to be careful about the pc we pass in here to
|
||||
// lookup frame information. This program counter is used to
|
||||
// translate back to an original source location in the origin wasm
|
||||
// module. If this pc is the exact pc that the trap happened at,
|
||||
// then we look up that pc precisely. Otherwise backtrace
|
||||
// information typically points at the pc *after* the call
|
||||
// instruction (because otherwise it's likely a call instruction on
|
||||
// the stack). In that case we want to lookup information for the
|
||||
// previous instruction (the call instruction) so we subtract one as
|
||||
// the lookup.
|
||||
let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 };
|
||||
Some(pc_to_lookup)
|
||||
}
|
||||
})
|
||||
.filter_map(|pc| info.lookup_frame_info(pc))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
inner: Arc::new(RuntimeErrorInner {
|
||||
source,
|
||||
wasm_trace,
|
||||
native_trace,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference the `message` stored in `Trap`.
|
||||
pub fn message(&self) -> String {
|
||||
self.inner.source.to_string()
|
||||
}
|
||||
|
||||
/// Returns a list of function frames in WebAssembly code that led to this
|
||||
/// trap happening.
|
||||
pub fn trace(&self) -> &[FrameInfo] {
|
||||
&self.inner.wasm_trace
|
||||
}
|
||||
|
||||
/// Attempts to downcast the `RuntimeError` to a concrete type.
|
||||
pub fn downcast<T: Error + 'static>(self) -> Result<T, Self> {
|
||||
match Arc::try_unwrap(self.inner) {
|
||||
// We only try to downcast user errors
|
||||
Ok(RuntimeErrorInner {
|
||||
source: RuntimeErrorSource::User(err),
|
||||
..
|
||||
}) if err.is::<T>() => Ok(*err.downcast::<T>().unwrap()),
|
||||
Ok(inner) => Err(Self {
|
||||
inner: Arc::new(inner),
|
||||
}),
|
||||
Err(inner) => Err(Self { inner }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns trap code, if it's a Trap
|
||||
pub fn to_trap(self) -> Option<TrapCode> {
|
||||
if let RuntimeErrorSource::Trap(trap_code) = self.inner.source {
|
||||
Some(trap_code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the `RuntimeError` is the same as T
|
||||
pub fn is<T: Error + 'static>(&self) -> bool {
|
||||
match &self.inner.source {
|
||||
RuntimeErrorSource::User(err) => err.is::<T>(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RuntimeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RuntimeError")
|
||||
.field("source", &self.inner.source)
|
||||
.field("wasm_trace", &self.inner.wasm_trace)
|
||||
.field("native_trace", &self.inner.native_trace)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RuntimeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "RuntimeError: {}", self.message())?;
|
||||
let trace = self.trace();
|
||||
if trace.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
for frame in self.trace().iter() {
|
||||
let name = frame.module_name();
|
||||
let func_index = frame.func_index();
|
||||
writeln!(f)?;
|
||||
write!(f, " at ")?;
|
||||
match frame.function_name() {
|
||||
Some(name) => match rustc_demangle::try_demangle(name) {
|
||||
Ok(name) => write!(f, "{}", name)?,
|
||||
Err(_) => write!(f, "{}", name)?,
|
||||
},
|
||||
None => write!(f, "<unnamed>")?,
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
" ({}[{}]:0x{:x})",
|
||||
name,
|
||||
func_index,
|
||||
frame.module_offset()
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RuntimeError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match &self.inner.source {
|
||||
RuntimeErrorSource::User(err) => Some(&**err),
|
||||
RuntimeErrorSource::Trap(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Trap> for RuntimeError {
|
||||
fn from(trap: Trap) -> Self {
|
||||
Self::from_trap(trap)
|
||||
}
|
||||
}
|
||||
318
lib/compiler/src/engine/trap/frame_info.rs
Normal file
318
lib/compiler/src/engine/trap/frame_info.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
//! This module is used for having backtraces in the Wasm runtime.
|
||||
//! Once the Compiler has compiled the ModuleInfo, and we have a set of
|
||||
//! compiled functions (addresses and function index) and a module,
|
||||
//! then we can use this to set a backtrace for that module.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```ignore
|
||||
//! use wasmer_vm::{FRAME_INFO};
|
||||
//! use wasmer_types::ModuleInfo;
|
||||
//!
|
||||
//! let module: ModuleInfo = ...;
|
||||
//! FRAME_INFO.register(module, compiled_functions);
|
||||
//! ```
|
||||
use std::cmp;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap};
|
||||
use wasmer_types::{CompiledFunctionFrameInfo, SourceLoc, TrapInformation};
|
||||
use wasmer_types::{LocalFunctionIndex, ModuleInfo};
|
||||
use wasmer_vm::FunctionBodyPtr;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// This is a global cache of backtrace frame information for all active
|
||||
///
|
||||
/// This global cache is used during `Trap` creation to symbolicate frames.
|
||||
/// This is populated on module compilation, and it is cleared out whenever
|
||||
/// all references to a module are dropped.
|
||||
pub static ref FRAME_INFO: RwLock<GlobalFrameInfo> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GlobalFrameInfo {
|
||||
/// An internal map that keeps track of backtrace frame information for
|
||||
/// each module.
|
||||
///
|
||||
/// This map is morally a map of ranges to a map of information for that
|
||||
/// module. Each module is expected to reside in a disjoint section of
|
||||
/// contiguous memory. No modules can overlap.
|
||||
///
|
||||
/// The key of this map is the highest address in the module and the value
|
||||
/// is the module's information, which also contains the start address.
|
||||
ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
|
||||
}
|
||||
|
||||
/// An RAII structure used to unregister a module's frame information when the
|
||||
/// module is destroyed.
|
||||
pub struct GlobalFrameInfoRegistration {
|
||||
/// The key that will be removed from the global `ranges` map when this is
|
||||
/// dropped.
|
||||
key: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ModuleInfoFrameInfo {
|
||||
start: usize,
|
||||
functions: BTreeMap<usize, FunctionInfo>,
|
||||
module: Arc<ModuleInfo>,
|
||||
frame_infos: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
|
||||
}
|
||||
|
||||
impl ModuleInfoFrameInfo {
|
||||
fn function_debug_info(&self, local_index: LocalFunctionIndex) -> &CompiledFunctionFrameInfo {
|
||||
self.frame_infos.get(local_index).unwrap()
|
||||
}
|
||||
|
||||
/// Gets a function given a pc
|
||||
fn function_info(&self, pc: usize) -> Option<&FunctionInfo> {
|
||||
let (end, func) = self.functions.range(pc..).next()?;
|
||||
if func.start <= pc && pc <= *end {
|
||||
Some(func)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FunctionInfo {
|
||||
start: usize,
|
||||
local_index: LocalFunctionIndex,
|
||||
}
|
||||
|
||||
impl GlobalFrameInfo {
|
||||
/// Fetches frame information about a program counter in a backtrace.
|
||||
///
|
||||
/// Returns an object if this `pc` is known to some previously registered
|
||||
/// module, or returns `None` if no information can be found.
|
||||
pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
|
||||
let module = self.module_info(pc)?;
|
||||
let func = module.function_info(pc)?;
|
||||
|
||||
// Use our relative position from the start of the function to find the
|
||||
// machine instruction that corresponds to `pc`, which then allows us to
|
||||
// map that to a wasm original source location.
|
||||
let rel_pos = pc - func.start;
|
||||
let instr_map = &module.function_debug_info(func.local_index).address_map;
|
||||
let pos = match instr_map
|
||||
.instructions
|
||||
.binary_search_by_key(&rel_pos, |map| map.code_offset)
|
||||
{
|
||||
// Exact hit!
|
||||
Ok(pos) => Some(pos),
|
||||
|
||||
// This *would* be at the first slot in the array, so no
|
||||
// instructions cover `pc`.
|
||||
Err(0) => None,
|
||||
|
||||
// This would be at the `nth` slot, so check `n-1` to see if we're
|
||||
// part of that instruction. This happens due to the minus one when
|
||||
// this function is called form trap symbolication, where we don't
|
||||
// always get called with a `pc` that's an exact instruction
|
||||
// boundary.
|
||||
Err(n) => {
|
||||
let instr = &instr_map.instructions[n - 1];
|
||||
if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
|
||||
Some(n - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let instr = match pos {
|
||||
Some(pos) => instr_map.instructions[pos].srcloc,
|
||||
// Some compilers don't emit yet the full trap information for each of
|
||||
// the instructions (such as LLVM).
|
||||
// In case no specific instruction is found, we return by default the
|
||||
// start offset of the function.
|
||||
None => instr_map.start_srcloc,
|
||||
};
|
||||
let func_index = module.module.func_index(func.local_index);
|
||||
Some(FrameInfo {
|
||||
module_name: module.module.name(),
|
||||
func_index: func_index.index() as u32,
|
||||
function_name: module.module.function_names.get(&func_index).cloned(),
|
||||
instr,
|
||||
func_start: instr_map.start_srcloc,
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches trap information about a program counter in a backtrace.
|
||||
pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
|
||||
let module = self.module_info(pc)?;
|
||||
let func = module.function_info(pc)?;
|
||||
let traps = &module.function_debug_info(func.local_index).traps;
|
||||
let idx = traps
|
||||
.binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
|
||||
.ok()?;
|
||||
Some(&traps[idx])
|
||||
}
|
||||
|
||||
/// Gets a module given a pc
|
||||
fn module_info(&self, pc: usize) -> Option<&ModuleInfoFrameInfo> {
|
||||
let (end, module_info) = self.ranges.range(pc..).next()?;
|
||||
if module_info.start <= pc && pc <= *end {
|
||||
Some(module_info)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlobalFrameInfoRegistration {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut info) = FRAME_INFO.write() {
|
||||
info.ranges.remove(&self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a continuous region of executable memory starting with a function
|
||||
/// entry point.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct FunctionExtent {
|
||||
/// Entry point for normal entry of the function. All addresses in the
|
||||
/// function lie after this address.
|
||||
pub ptr: FunctionBodyPtr,
|
||||
/// Length in bytes.
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
/// Registers a new compiled module's frame information.
|
||||
///
|
||||
/// This function will register the `names` information for all of the
|
||||
/// compiled functions within `module`. If the `module` has no functions
|
||||
/// then `None` will be returned. Otherwise the returned object, when
|
||||
/// dropped, will be used to unregister all name information from this map.
|
||||
pub fn register(
|
||||
module: Arc<ModuleInfo>,
|
||||
finished_functions: &BoxedSlice<LocalFunctionIndex, FunctionExtent>,
|
||||
frame_infos: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
|
||||
) -> Option<GlobalFrameInfoRegistration> {
|
||||
let mut min = usize::max_value();
|
||||
let mut max = 0;
|
||||
let mut functions = BTreeMap::new();
|
||||
for (
|
||||
i,
|
||||
FunctionExtent {
|
||||
ptr: start,
|
||||
length: len,
|
||||
},
|
||||
) in finished_functions.iter()
|
||||
{
|
||||
let start = **start as usize;
|
||||
let end = start + len;
|
||||
min = cmp::min(min, start);
|
||||
max = cmp::max(max, end);
|
||||
let func = FunctionInfo {
|
||||
start,
|
||||
local_index: i,
|
||||
};
|
||||
assert!(functions.insert(end, func).is_none());
|
||||
}
|
||||
if functions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut info = FRAME_INFO.write().unwrap();
|
||||
// First up assert that our chunk of jit functions doesn't collide with
|
||||
// any other known chunks of jit functions...
|
||||
if let Some((_, prev)) = info.ranges.range(max..).next() {
|
||||
assert!(prev.start > max);
|
||||
}
|
||||
if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() {
|
||||
assert!(*prev_end < min);
|
||||
}
|
||||
|
||||
// ... then insert our range and assert nothing was there previously
|
||||
let prev = info.ranges.insert(
|
||||
max,
|
||||
ModuleInfoFrameInfo {
|
||||
start: min,
|
||||
functions,
|
||||
module,
|
||||
frame_infos,
|
||||
},
|
||||
);
|
||||
assert!(prev.is_none());
|
||||
Some(GlobalFrameInfoRegistration { key: max })
|
||||
}
|
||||
|
||||
/// Description of a frame in a backtrace for a [`RuntimeError::trace`](crate::RuntimeError::trace).
|
||||
///
|
||||
/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`]
|
||||
/// is created. Each [`RuntimeError`] has a backtrace of the
|
||||
/// WebAssembly frames that led to the trap, and each frame is
|
||||
/// described by this structure.
|
||||
///
|
||||
/// [`RuntimeError`]: crate::RuntimeError
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameInfo {
|
||||
module_name: String,
|
||||
func_index: u32,
|
||||
function_name: Option<String>,
|
||||
func_start: SourceLoc,
|
||||
instr: SourceLoc,
|
||||
}
|
||||
|
||||
impl FrameInfo {
|
||||
/// Returns the WebAssembly function index for this frame.
|
||||
///
|
||||
/// This function index is the index in the function index space of the
|
||||
/// WebAssembly module that this frame comes from.
|
||||
pub fn func_index(&self) -> u32 {
|
||||
self.func_index
|
||||
}
|
||||
|
||||
/// Returns the identifer of the module that this frame is for.
|
||||
///
|
||||
/// ModuleInfo identifiers are present in the `name` section of a WebAssembly
|
||||
/// binary, but this may not return the exact item in the `name` section.
|
||||
/// ModuleInfo names can be overwritten at construction time or perhaps inferred
|
||||
/// from file names. The primary purpose of this function is to assist in
|
||||
/// debugging and therefore may be tweaked over time.
|
||||
///
|
||||
/// This function returns `None` when no name can be found or inferred.
|
||||
pub fn module_name(&self) -> &str {
|
||||
&self.module_name
|
||||
}
|
||||
|
||||
/// Returns a descriptive name of the function for this frame, if one is
|
||||
/// available.
|
||||
///
|
||||
/// The name of this function may come from the `name` section of the
|
||||
/// WebAssembly binary, or wasmer may try to infer a better name for it if
|
||||
/// not available, for example the name of the export if it's exported.
|
||||
///
|
||||
/// This return value is primarily used for debugging and human-readable
|
||||
/// purposes for things like traps. Note that the exact return value may be
|
||||
/// tweaked over time here and isn't guaranteed to be something in
|
||||
/// particular about a wasm module due to its primary purpose of assisting
|
||||
/// in debugging.
|
||||
///
|
||||
/// This function returns `None` when no name could be inferred.
|
||||
pub fn function_name(&self) -> Option<&str> {
|
||||
self.function_name.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the offset within the original wasm module this frame's program
|
||||
/// counter was at.
|
||||
///
|
||||
/// The offset here is the offset from the beginning of the original wasm
|
||||
/// module to the instruction that this frame points to.
|
||||
pub fn module_offset(&self) -> usize {
|
||||
self.instr.bits() as usize
|
||||
}
|
||||
|
||||
/// Returns the offset from the original wasm module's function to this
|
||||
/// frame's program counter.
|
||||
///
|
||||
/// The offset here is the offset from the beginning of the defining
|
||||
/// function of this frame (within the wasm module) to the instruction this
|
||||
/// frame points to.
|
||||
pub fn func_offset(&self) -> usize {
|
||||
(self.instr.bits() - self.func_start.bits()) as usize
|
||||
}
|
||||
}
|
||||
7
lib/compiler/src/engine/trap/mod.rs
Normal file
7
lib/compiler/src/engine/trap/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod error;
|
||||
mod frame_info;
|
||||
pub use error::RuntimeError;
|
||||
pub use frame_info::{
|
||||
register as register_frame_info, FrameInfo, FunctionExtent, GlobalFrameInfoRegistration,
|
||||
FRAME_INFO,
|
||||
};
|
||||
143
lib/compiler/src/engine/tunables.rs
Normal file
143
lib/compiler/src/engine/tunables.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use crate::engine::error::LinkError;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::Arc;
|
||||
use wasmer_types::entity::{EntityRef, PrimaryMap};
|
||||
use wasmer_types::{
|
||||
GlobalType, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, MemoryType,
|
||||
ModuleInfo, TableIndex, TableType,
|
||||
};
|
||||
use wasmer_vm::MemoryError;
|
||||
use wasmer_vm::{Global, Memory, Table};
|
||||
use wasmer_vm::{MemoryStyle, TableStyle};
|
||||
use wasmer_vm::{VMMemoryDefinition, VMTableDefinition};
|
||||
|
||||
/// An engine delegates the creation of memories, tables, and globals
|
||||
/// to a foreign implementor of this trait.
|
||||
pub trait Tunables {
|
||||
/// Construct a `MemoryStyle` for the provided `MemoryType`
|
||||
fn memory_style(&self, memory: &MemoryType) -> MemoryStyle;
|
||||
|
||||
/// Construct a `TableStyle` for the provided `TableType`
|
||||
fn table_style(&self, table: &TableType) -> TableStyle;
|
||||
|
||||
/// Create a memory owned by the host given a [`MemoryType`] and a [`MemoryStyle`].
|
||||
fn create_host_memory(
|
||||
&self,
|
||||
ty: &MemoryType,
|
||||
style: &MemoryStyle,
|
||||
) -> Result<Arc<dyn Memory>, MemoryError>;
|
||||
|
||||
/// Create a memory owned by the VM given a [`MemoryType`] and a [`MemoryStyle`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - `vm_definition_location` must point to a valid location in VM memory.
|
||||
unsafe fn create_vm_memory(
|
||||
&self,
|
||||
ty: &MemoryType,
|
||||
style: &MemoryStyle,
|
||||
vm_definition_location: NonNull<VMMemoryDefinition>,
|
||||
) -> Result<Arc<dyn Memory>, MemoryError>;
|
||||
|
||||
/// Create a table owned by the host given a [`TableType`] and a [`TableStyle`].
|
||||
fn create_host_table(
|
||||
&self,
|
||||
ty: &TableType,
|
||||
style: &TableStyle,
|
||||
) -> Result<Arc<dyn Table>, String>;
|
||||
|
||||
/// Create a table owned by the VM given a [`TableType`] and a [`TableStyle`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - `vm_definition_location` must point to a valid location in VM memory.
|
||||
unsafe fn create_vm_table(
|
||||
&self,
|
||||
ty: &TableType,
|
||||
style: &TableStyle,
|
||||
vm_definition_location: NonNull<VMTableDefinition>,
|
||||
) -> Result<Arc<dyn Table>, String>;
|
||||
|
||||
/// Create a global with an unset value.
|
||||
fn create_global(&self, ty: GlobalType) -> Result<Arc<Global>, String> {
|
||||
Ok(Arc::new(Global::new(ty)))
|
||||
}
|
||||
|
||||
/// Allocate memory for just the memories of the current module.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `memory_definition_locations` must point to a valid locations in VM memory.
|
||||
unsafe fn create_memories(
|
||||
&self,
|
||||
module: &ModuleInfo,
|
||||
memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
|
||||
memory_definition_locations: &[NonNull<VMMemoryDefinition>],
|
||||
) -> Result<PrimaryMap<LocalMemoryIndex, Arc<dyn Memory>>, LinkError> {
|
||||
let num_imports = module.num_imported_memories;
|
||||
let mut memories: PrimaryMap<LocalMemoryIndex, _> =
|
||||
PrimaryMap::with_capacity(module.memories.len() - num_imports);
|
||||
for (index, mdl) in memory_definition_locations
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(module.memories.len())
|
||||
.skip(num_imports)
|
||||
{
|
||||
let mi = MemoryIndex::new(index);
|
||||
let ty = &module.memories[mi];
|
||||
let style = &memory_styles[mi];
|
||||
memories.push(
|
||||
self.create_vm_memory(ty, style, *mdl)
|
||||
.map_err(|e| LinkError::Resource(format!("Failed to create memory: {}", e)))?,
|
||||
);
|
||||
}
|
||||
Ok(memories)
|
||||
}
|
||||
|
||||
/// Allocate memory for just the tables of the current module.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// To be done
|
||||
unsafe fn create_tables(
|
||||
&self,
|
||||
module: &ModuleInfo,
|
||||
table_styles: &PrimaryMap<TableIndex, TableStyle>,
|
||||
table_definition_locations: &[NonNull<VMTableDefinition>],
|
||||
) -> Result<PrimaryMap<LocalTableIndex, Arc<dyn Table>>, LinkError> {
|
||||
let num_imports = module.num_imported_tables;
|
||||
let mut tables: PrimaryMap<LocalTableIndex, _> =
|
||||
PrimaryMap::with_capacity(module.tables.len() - num_imports);
|
||||
for (index, tdl) in table_definition_locations
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(module.tables.len())
|
||||
.skip(num_imports)
|
||||
{
|
||||
let ti = TableIndex::new(index);
|
||||
let ty = &module.tables[ti];
|
||||
let style = &table_styles[ti];
|
||||
tables.push(
|
||||
self.create_vm_table(ty, style, *tdl)
|
||||
.map_err(LinkError::Resource)?,
|
||||
);
|
||||
}
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
/// Allocate memory for just the globals of the current module,
|
||||
/// with initializers applied.
|
||||
fn create_globals(
|
||||
&self,
|
||||
module: &ModuleInfo,
|
||||
) -> Result<PrimaryMap<LocalGlobalIndex, Arc<Global>>, LinkError> {
|
||||
let num_imports = module.num_imported_globals;
|
||||
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
||||
|
||||
for &global_type in module.globals.values().skip(num_imports) {
|
||||
vmctx_globals.push(
|
||||
self.create_global(global_type)
|
||||
.map_err(LinkError::Resource)?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(vmctx_globals)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@
|
||||
#![warn(unused_import_braces)]
|
||||
#![cfg_attr(feature = "std", deny(unstable_features))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(clippy::new_without_default, clippy::upper_case_acronyms)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
warn(
|
||||
@@ -48,6 +51,14 @@ mod lib {
|
||||
}
|
||||
}
|
||||
|
||||
mod artifact;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod engine;
|
||||
|
||||
pub use crate::artifact::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use crate::engine::*;
|
||||
|
||||
#[cfg(feature = "translator")]
|
||||
mod compiler;
|
||||
mod target;
|
||||
|
||||
Reference in New Issue
Block a user