feat(api) Merge js-api into api.

This patch takes the entire `wasmer-js` crate and merges it into the
`wasmer` crate.

Inside the `lib/api/src/` directory, there are 2 new directories:

1. a new `sys` directory, which contains the usual `wasmer` crate
   implementation,
2. a new directory `js`, which contains the implementation of
   `wasmer-js`.

The `Cargo.toml` file is still compatible. The `default` feature
fallbacks to `sys-default`, which enables the `sys` feature. All
features related to compilers or engines or anything else prior this
patch, activates the `sys` feature.

Parallel to that, there is a `js-default` and `js` features.

The `Cargo.toml` file is extensively documented to explain what are
dependencies, dev-dependencies, features and other sections related to
`sys` or to `js`.

There is a bug with `wasm_bindgen_test` where it doesn't compile or
look for tests in `tests/*/<test>.rs`. The hack is to name files
`tests/js_<test>.rs`. Ugly, but it works.
This commit is contained in:
Ivan Enderlin
2021-07-23 12:10:49 +02:00
parent 6b2ec7209d
commit b30284897e
65 changed files with 3808 additions and 3783 deletions

140
lib/api/src/js/cell.rs Normal file
View File

@@ -0,0 +1,140 @@
use core::cmp::Ordering;
use core::fmt::{self, Debug};
use std::marker::PhantomData;
use js_sys::Uint8Array;
/// A mutable Wasm-memory location.
pub struct WasmCell<'a, T: ?Sized> {
pub(crate) memory: Uint8Array,
#[allow(dead_code)]
phantom: &'a PhantomData<T>,
}
unsafe impl<T: ?Sized> Send for WasmCell<'_, T> where T: Send {}
unsafe impl<T: ?Sized> Sync for WasmCell<'_, T> {}
impl<'a, T: Copy> Clone for WasmCell<'a, T> {
#[inline]
fn clone(&self) -> WasmCell<'a, T> {
WasmCell {
memory: self.memory.clone(),
phantom: &PhantomData,
}
}
}
impl<T: PartialEq + Copy> PartialEq for WasmCell<'_, T> {
#[inline]
fn eq(&self, other: &WasmCell<T>) -> bool {
self.get() == other.get()
}
}
impl<T: Eq + Copy> Eq for WasmCell<'_, T> {}
impl<T: PartialOrd + Copy> PartialOrd for WasmCell<'_, T> {
#[inline]
fn partial_cmp(&self, other: &WasmCell<T>) -> Option<Ordering> {
self.get().partial_cmp(&other.get())
}
#[inline]
fn lt(&self, other: &WasmCell<T>) -> bool {
self.get() < other.get()
}
#[inline]
fn le(&self, other: &WasmCell<T>) -> bool {
self.get() <= other.get()
}
#[inline]
fn gt(&self, other: &WasmCell<T>) -> bool {
self.get() > other.get()
}
#[inline]
fn ge(&self, other: &WasmCell<T>) -> bool {
self.get() >= other.get()
}
}
impl<T: Ord + Copy> Ord for WasmCell<'_, T> {
#[inline]
fn cmp(&self, other: &WasmCell<T>) -> Ordering {
self.get().cmp(&other.get())
}
}
impl<'a, T> WasmCell<'a, T> {
/// Creates a new `WasmCell` containing the given value.
///
/// # Examples
///
/// ```
/// use std::cell::Cell;
/// use wasmer_js::WasmCell;
///
/// let cell = Cell::new(5);
/// let wasm_cell = WasmCell::new(&cell);
/// ```
#[inline]
pub const fn new(memory: Uint8Array) -> WasmCell<'a, T> {
WasmCell {
memory,
phantom: &PhantomData,
}
}
}
impl<'a, T: Copy> WasmCell<'a, T> {
/// Returns a copy of the contained value.
///
/// # Examples
///
/// ```
/// use std::cell::Cell;
/// use wasmer_js::WasmCell;
///
/// let cell = Cell::new(5);
/// let wasm_cell = WasmCell::new(&cell);
/// let five = wasm_cell.get();
/// ```
#[inline]
pub fn get(&self) -> T {
let vec = self.memory.to_vec();
unsafe { *(vec.as_ptr() as *const T) }
}
}
impl<T: Debug + Copy> Debug for WasmCell<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WasmCell({:?})", self.get())
}
}
impl<T: Sized> WasmCell<'_, T> {
/// Sets the contained value.
///
/// # Examples
///
/// ```
/// use std::cell::Cell;
/// use wasmer_js::WasmCell;
///
/// let cell = Cell::new(5);
/// let wasm_cell = WasmCell::new(&cell);
/// wasm_cell.set(10);
/// assert_eq!(cell.get(), 10);
/// ```
#[inline]
pub fn set(&self, val: T) {
let size = std::mem::size_of::<T>();
let ptr = &val as *const T as *const u8;
let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
self.memory.copy_from(slice);
}
}

219
lib/api/src/js/env.rs Normal file
View File

@@ -0,0 +1,219 @@
use crate::js::{ExportError, Instance};
use thiserror::Error;
/// An error while initializing the user supplied host env with the `WasmerEnv` trait.
#[derive(Error, Debug)]
#[error("Host env initialization error: {0}")]
pub enum HostEnvInitError {
/// An error occurred when accessing an export
Export(ExportError),
}
impl From<ExportError> for HostEnvInitError {
fn from(other: ExportError) -> Self {
Self::Export(other)
}
}
/// Trait for initializing the environments passed to host functions after
/// instantiation but before execution.
///
/// This is useful for filling an environment with data that can only be accesed
/// after instantiation. For example, exported items such as memories and
/// functions which don't exist prior to instantiation can be accessed here so
/// that host functions can use them.
///
/// # Examples
///
/// This trait can be derived like so:
///
/// ```
/// use wasmer_js::{WasmerEnv, LazyInit, Memory, NativeFunc};
///
/// #[derive(WasmerEnv, Clone)]
/// pub struct MyEnvWithNoInstanceData {
/// non_instance_data: u8,
/// }
///
/// #[derive(WasmerEnv, Clone)]
/// pub struct MyEnvWithInstanceData {
/// non_instance_data: u8,
/// #[wasmer(export)]
/// memory: LazyInit<Memory>,
/// #[wasmer(export(name = "real_name"))]
/// func: LazyInit<NativeFunc<(i32, i32), i32>>,
/// #[wasmer(export(optional = true, alias = "memory2", alias = "_memory2"))]
/// optional_memory: LazyInit<Memory>,
/// }
/// ```
///
/// When deriving `WasmerEnv`, you must wrap your types to be initialized in
/// [`LazyInit`]. The derive macro will also generate helper methods of the form
/// `<field_name>_ref` and `<field_name>_ref_unchecked` for easy access to the
/// data.
///
/// The valid arguments to `export` are:
/// - `name = "string"`: specify the name of this item in the Wasm module. If this is not specified, it will default to the name of the field.
/// - `optional = true`: specify whether this export is optional. Defaults to
/// `false`. Being optional means that if the export can't be found, the
/// [`LazyInit`] will be left uninitialized.
/// - `alias = "string"`: specify additional names to look for in the Wasm module.
/// `alias` may be specified multiple times to search for multiple aliases.
/// -------
///
/// This trait may also be implemented manually:
/// ```
/// # use wasmer_js::{WasmerEnv, LazyInit, Memory, Instance, HostEnvInitError};
/// #[derive(Clone)]
/// pub struct MyEnv {
/// memory: LazyInit<Memory>,
/// }
///
/// impl WasmerEnv for MyEnv {
/// fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> {
/// let memory: Memory = instance.exports.get_with_generics_weak("memory").unwrap();
/// self.memory.initialize(memory.clone());
/// Ok(())
/// }
/// }
/// ```
pub trait WasmerEnv {
/// The function that Wasmer will call on your type to let it finish
/// setting up the environment with data from the `Instance`.
///
/// This function is called after `Instance` is created but before it is
/// returned to the user via `Instance::new`.
fn init_with_instance(&mut self, _instance: &Instance) -> Result<(), HostEnvInitError> {
Ok(())
}
}
impl WasmerEnv for u8 {}
impl WasmerEnv for i8 {}
impl WasmerEnv for u16 {}
impl WasmerEnv for i16 {}
impl WasmerEnv for u32 {}
impl WasmerEnv for i32 {}
impl WasmerEnv for u64 {}
impl WasmerEnv for i64 {}
impl WasmerEnv for u128 {}
impl WasmerEnv for i128 {}
impl WasmerEnv for f32 {}
impl WasmerEnv for f64 {}
impl WasmerEnv for usize {}
impl WasmerEnv for isize {}
impl WasmerEnv for char {}
impl WasmerEnv for bool {}
impl WasmerEnv for String {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicBool {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicI8 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicU8 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicI16 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicU16 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicI32 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicU32 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicI64 {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicUsize {}
impl<'a> WasmerEnv for &'a ::std::sync::atomic::AtomicIsize {}
impl<T: WasmerEnv> WasmerEnv for Box<T> {
fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> {
(&mut **self).init_with_instance(instance)
}
}
impl<T: WasmerEnv> WasmerEnv for ::std::sync::Arc<::std::sync::Mutex<T>> {
fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> {
let mut guard = self.lock().unwrap();
guard.init_with_instance(instance)
}
}
/// Lazily init an item
pub struct LazyInit<T: Sized> {
/// The data to be initialized
data: std::mem::MaybeUninit<T>,
/// Whether or not the data has been initialized
initialized: bool,
}
impl<T> LazyInit<T> {
/// Creates an unitialized value.
pub fn new() -> Self {
Self {
data: std::mem::MaybeUninit::uninit(),
initialized: false,
}
}
/// # Safety
/// - The data must be initialized first
pub unsafe fn get_unchecked(&self) -> &T {
&*self.data.as_ptr()
}
/// Get the inner data.
pub fn get_ref(&self) -> Option<&T> {
if !self.initialized {
None
} else {
Some(unsafe { self.get_unchecked() })
}
}
/// Sets a value and marks the data as initialized.
pub fn initialize(&mut self, value: T) -> bool {
if self.initialized {
return false;
}
unsafe {
self.data.as_mut_ptr().write(value);
}
self.initialized = true;
true
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for LazyInit<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("LazyInit")
.field("data", &self.get_ref())
.finish()
}
}
impl<T: Clone> Clone for LazyInit<T> {
fn clone(&self) -> Self {
if let Some(inner) = self.get_ref() {
Self {
data: std::mem::MaybeUninit::new(inner.clone()),
initialized: true,
}
} else {
Self {
data: std::mem::MaybeUninit::uninit(),
initialized: false,
}
}
}
}
impl<T> Drop for LazyInit<T> {
fn drop(&mut self) {
if self.initialized {
unsafe {
let ptr = self.data.as_mut_ptr();
std::ptr::drop_in_place(ptr);
};
}
}
}
impl<T> Default for LazyInit<T> {
fn default() -> Self {
Self::new()
}
}
unsafe impl<T: Send> Send for LazyInit<T> {}
// I thought we could opt out of sync..., look into this
// unsafe impl<T> !Sync for InitWithInstance<T> {}

89
lib/api/src/js/error.rs Normal file
View File

@@ -0,0 +1,89 @@
use crate::js::lib::std::string::String;
#[cfg(feature = "std")]
use thiserror::Error;
// Compilation Errors
//
// If `std` feature is enable, we can't use `thiserror` until
// https://github.com/dtolnay/thiserror/pull/64 is merged.
/// The WebAssembly.CompileError object indicates an error during
/// WebAssembly decoding or validation.
///
/// This is based on the [Wasm Compile Error][compile-error] API.
///
/// [compiler-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/CompileError
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
pub enum CompileError {
/// A Wasm translation error occured.
#[cfg_attr(feature = "std", error("WebAssembly translation error: {0}"))]
Wasm(WasmError),
/// A compilation error occured.
#[cfg_attr(feature = "std", error("Compilation error: {0}"))]
Codegen(String),
/// The module did not pass validation.
#[cfg_attr(feature = "std", error("Validation error: {0}"))]
Validate(String),
/// The compiler doesn't support a Wasm feature
#[cfg_attr(feature = "std", error("Feature {0} is not yet supported"))]
UnsupportedFeature(String),
/// The compiler cannot compile for the given target.
/// This can refer to the OS, the chipset or any other aspect of the target system.
#[cfg_attr(feature = "std", error("The target {0} is not yet supported (see https://docs.wasmer.io/ecosystem/wasmer/wasmer-features)"))]
UnsupportedTarget(String),
/// Insufficient resources available for execution.
#[cfg_attr(feature = "std", error("Insufficient resources: {0}"))]
Resource(String),
}
#[cfg(feature = "core")]
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CompileError")
}
}
impl From<WasmError> for CompileError {
fn from(original: WasmError) -> Self {
Self::Wasm(original)
}
}
/// A WebAssembly translation error.
///
/// When a WebAssembly function can't be translated, one of these error codes will be returned
/// to describe the failure.
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
pub enum WasmError {
/// The input WebAssembly code is invalid.
///
/// This error code is used by a WebAssembly translator when it encounters invalid WebAssembly
/// code. This should never happen for validated WebAssembly code.
#[cfg_attr(
feature = "std",
error("Invalid input WebAssembly code at offset {offset}: {message}")
)]
InvalidWebAssembly {
/// A string describing the validation error.
message: String,
/// The bytecode offset where the error occurred.
offset: usize,
},
/// A feature used by the WebAssembly code is not supported by the embedding environment.
///
/// Embedding environments may have their own limitations and feature restrictions.
#[cfg_attr(feature = "std", error("Unsupported feature: {0}"))]
Unsupported(String),
/// A generic error.
#[cfg_attr(feature = "std", error("{0}"))]
Generic(String),
}

162
lib/api/src/js/export.rs Normal file
View File

@@ -0,0 +1,162 @@
use crate::js::instance::Instance;
use crate::js::wasm_bindgen_polyfill::Global;
use crate::js::HostEnvInitError;
use crate::js::WasmerEnv;
use js_sys::Function;
use js_sys::WebAssembly::{Memory, Table};
use std::cell::RefCell;
use std::fmt;
use std::sync::Arc;
use wasm_bindgen::{JsCast, JsValue};
use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType};
#[derive(Clone, Debug, PartialEq)]
pub struct VMMemory {
pub(crate) memory: Memory,
pub(crate) ty: MemoryType,
}
impl VMMemory {
pub(crate) fn new(memory: Memory, ty: MemoryType) -> Self {
Self { memory, ty }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VMGlobal {
pub(crate) global: Global,
pub(crate) ty: GlobalType,
}
impl VMGlobal {
pub(crate) fn new(global: Global, ty: GlobalType) -> Self {
Self { global, ty }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VMTable {
pub(crate) table: Table,
pub(crate) ty: TableType,
}
impl VMTable {
pub(crate) fn new(table: Table, ty: TableType) -> Self {
Self { table, ty }
}
}
#[derive(Clone)]
pub struct VMFunction {
pub(crate) function: Function,
pub(crate) ty: FunctionType,
pub(crate) environment: Option<Arc<RefCell<Box<dyn WasmerEnv>>>>,
}
impl VMFunction {
pub(crate) fn new(
function: Function,
ty: FunctionType,
environment: Option<Box<dyn WasmerEnv>>,
) -> Self {
Self {
function,
ty,
environment: environment.map(|env| Arc::new(RefCell::new(env))),
}
}
pub(crate) fn init_envs(&self, instance: &Instance) -> Result<(), HostEnvInitError> {
if let Some(env) = &self.environment {
let mut borrowed_env = env.borrow_mut();
borrowed_env.init_with_instance(instance)?;
}
Ok(())
}
}
impl PartialEq for VMFunction {
fn eq(&self, other: &Self) -> bool {
self.function == other.function
}
}
impl fmt::Debug for VMFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VMFunction")
.field("function", &self.function)
.finish()
}
}
/// The value of an export passed from one instance to another.
#[derive(Debug, Clone)]
pub enum Export {
/// A function export value.
Function(VMFunction),
/// A table export value.
Table(VMTable),
/// A memory export value.
Memory(VMMemory),
/// A global export value.
Global(VMGlobal),
}
impl Export {
pub fn as_jsvalue(&self) -> &JsValue {
match self {
Export::Memory(js_wasm_memory) => js_wasm_memory.memory.as_ref(),
Export::Function(js_func) => js_func.function.as_ref(),
Export::Table(js_wasm_table) => js_wasm_table.table.as_ref(),
Export::Global(js_wasm_global) => js_wasm_global.global.as_ref(),
}
}
}
impl From<(JsValue, ExternType)> for Export {
fn from((val, extern_type): (JsValue, ExternType)) -> Export {
match extern_type {
ExternType::Memory(memory_type) => {
if val.is_instance_of::<Memory>() {
return Export::Memory(VMMemory::new(
val.unchecked_into::<Memory>(),
memory_type,
));
} else {
panic!("Extern type doesn't match js value type");
}
}
ExternType::Global(global_type) => {
if val.is_instance_of::<Global>() {
return Export::Global(VMGlobal::new(
val.unchecked_into::<Global>(),
global_type,
));
} else {
panic!("Extern type doesn't match js value type");
}
}
ExternType::Function(function_type) => {
if val.is_instance_of::<Function>() {
return Export::Function(VMFunction::new(
val.unchecked_into::<Function>(),
function_type,
None,
));
} else {
panic!("Extern type doesn't match js value type");
}
}
ExternType::Table(table_type) => {
if val.is_instance_of::<Table>() {
return Export::Table(VMTable::new(val.unchecked_into::<Table>(), table_type));
} else {
panic!("Extern type doesn't match js value type");
}
}
}
}
}

324
lib/api/src/js/exports.rs Normal file
View File

@@ -0,0 +1,324 @@
use crate::js::export::Export;
use crate::js::externals::{Extern, Function, Global, Memory, Table};
use crate::js::import_object::LikeNamespace;
use crate::js::native::NativeFunc;
use crate::js::WasmTypeList;
use indexmap::IndexMap;
use std::fmt;
use std::iter::{ExactSizeIterator, FromIterator};
use std::sync::Arc;
use thiserror::Error;
/// The `ExportError` can happen when trying to get a specific
/// export [`Extern`] from the [`Instance`] exports.
///
/// [`Instance`]: crate::js::Instance
///
/// # Examples
///
/// ## Incompatible export type
///
/// ```should_panic
/// # use wasmer_js::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value, ExportError};
/// # let store = Store::default();
/// # let wasm_bytes = wat2wasm(r#"
/// # (module
/// # (global $one (export "glob") f32 (f32.const 1)))
/// # "#.as_bytes()).unwrap();
/// # let module = Module::new(&store, wasm_bytes).unwrap();
/// # let import_object = imports! {};
/// # let instance = Instance::new(&module, &import_object).unwrap();
/// #
/// // This results with an error: `ExportError::IncompatibleType`.
/// let export = instance.exports.get_function("glob").unwrap();
/// ```
///
/// ## Missing export
///
/// ```should_panic
/// # use wasmer_js::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value, ExportError};
/// # let store = Store::default();
/// # let wasm_bytes = wat2wasm("(module)".as_bytes()).unwrap();
/// # let module = Module::new(&store, wasm_bytes).unwrap();
/// # let import_object = imports! {};
/// # let instance = Instance::new(&module, &import_object).unwrap();
/// #
/// // This results with an error: `ExportError::Missing`.
/// let export = instance.exports.get_function("unknown").unwrap();
/// ```
#[derive(Error, Debug)]
pub enum ExportError {
/// An error than occurs when the exported type and the expected type
/// are incompatible.
#[error("Incompatible Export Type")]
IncompatibleType,
/// This error arises when an export is missing
#[error("Missing export {0}")]
Missing(String),
}
/// Exports is a special kind of map that allows easily unwrapping
/// the types of instances.
///
/// TODO: add examples of using exports
#[derive(Clone, Default)]
pub struct Exports {
map: Arc<IndexMap<String, Extern>>,
}
impl Exports {
/// Creates a new `Exports`.
pub fn new() -> Self {
Default::default()
}
/// Creates a new `Exports` with capacity `n`.
pub fn with_capacity(n: usize) -> Self {
Self {
map: Arc::new(IndexMap::with_capacity(n)),
}
}
/// Return the number of exports in the `Exports` map.
pub fn len(&self) -> usize {
self.map.len()
}
/// Return whether or not there are no exports
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Insert a new export into this `Exports` map.
pub fn insert<S, E>(&mut self, name: S, value: E)
where
S: Into<String>,
E: Into<Extern>,
{
Arc::get_mut(&mut self.map)
.unwrap()
.insert(name.into(), value.into());
}
/// Get an export given a `name`.
///
/// The `get` method is specifically made for usage inside of
/// Rust APIs, as we can detect what's the desired type easily.
///
/// If you want to get an export dynamically with type checking
/// please use the following functions: `get_func`, `get_memory`,
/// `get_table` or `get_global` instead.
///
/// If you want to get an export dynamically handling manually
/// type checking manually, please use `get_extern`.
pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> Result<&'a T, ExportError> {
match self.map.get(name) {
None => Err(ExportError::Missing(name.to_string())),
Some(extern_) => T::get_self_from_extern(extern_),
}
}
/// Get an export as a `Global`.
pub fn get_global(&self, name: &str) -> Result<&Global, ExportError> {
self.get(name)
}
/// Get an export as a `Memory`.
pub fn get_memory(&self, name: &str) -> Result<&Memory, ExportError> {
self.get(name)
}
/// Get an export as a `Table`.
pub fn get_table(&self, name: &str) -> Result<&Table, ExportError> {
self.get(name)
}
/// Get an export as a `Func`.
pub fn get_function(&self, name: &str) -> Result<&Function, ExportError> {
self.get(name)
}
/// Get an export as a `NativeFunc`.
pub fn get_native_function<Args, Rets>(
&self,
name: &str,
) -> Result<NativeFunc<Args, Rets>, ExportError>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
self.get_function(name)?
.native()
.map_err(|_| ExportError::IncompatibleType)
}
/// Hack to get this working with nativefunc too
pub fn get_with_generics<'a, T, Args, Rets>(&'a self, name: &str) -> Result<T, ExportError>
where
Args: WasmTypeList,
Rets: WasmTypeList,
T: ExportableWithGenerics<'a, Args, Rets>,
{
match self.map.get(name) {
None => Err(ExportError::Missing(name.to_string())),
Some(extern_) => T::get_self_from_extern_with_generics(extern_),
}
}
/// Like `get_with_generics` but with a WeakReference to the `InstanceRef` internally.
/// This is useful for passing data into `WasmerEnv`, for example.
pub fn get_with_generics_weak<'a, T, Args, Rets>(&'a self, name: &str) -> Result<T, ExportError>
where
Args: WasmTypeList,
Rets: WasmTypeList,
T: ExportableWithGenerics<'a, Args, Rets>,
{
let out: T = self.get_with_generics(name)?;
Ok(out)
}
/// Get an export as an `Extern`.
pub fn get_extern(&self, name: &str) -> Option<&Extern> {
self.map.get(name)
}
/// Returns true if the `Exports` contains the given export name.
pub fn contains<S>(&self, name: S) -> bool
where
S: Into<String>,
{
self.map.contains_key(&name.into())
}
/// Get an iterator over the exports.
pub fn iter(&self) -> ExportsIterator<impl Iterator<Item = (&String, &Extern)>> {
ExportsIterator {
iter: self.map.iter(),
}
}
}
impl fmt::Debug for Exports {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
/// An iterator over exports.
pub struct ExportsIterator<'a, I>
where
I: Iterator<Item = (&'a String, &'a Extern)> + Sized,
{
iter: I,
}
impl<'a, I> Iterator for ExportsIterator<'a, I>
where
I: Iterator<Item = (&'a String, &'a Extern)> + Sized,
{
type Item = (&'a String, &'a Extern);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
impl<'a, I> ExactSizeIterator for ExportsIterator<'a, I>
where
I: Iterator<Item = (&'a String, &'a Extern)> + ExactSizeIterator + Sized,
{
fn len(&self) -> usize {
self.iter.len()
}
}
impl<'a, I> ExportsIterator<'a, I>
where
I: Iterator<Item = (&'a String, &'a Extern)> + Sized,
{
/// Get only the functions.
pub fn functions(self) -> impl Iterator<Item = (&'a String, &'a Function)> + Sized {
self.iter.filter_map(|(name, export)| match export {
Extern::Function(function) => Some((name, function)),
_ => None,
})
}
/// Get only the memories.
pub fn memories(self) -> impl Iterator<Item = (&'a String, &'a Memory)> + Sized {
self.iter.filter_map(|(name, export)| match export {
Extern::Memory(memory) => Some((name, memory)),
_ => None,
})
}
/// Get only the globals.
pub fn globals(self) -> impl Iterator<Item = (&'a String, &'a Global)> + Sized {
self.iter.filter_map(|(name, export)| match export {
Extern::Global(global) => Some((name, global)),
_ => None,
})
}
/// Get only the tables.
pub fn tables(self) -> impl Iterator<Item = (&'a String, &'a Table)> + Sized {
self.iter.filter_map(|(name, export)| match export {
Extern::Table(table) => Some((name, table)),
_ => None,
})
}
}
impl FromIterator<(String, Extern)> for Exports {
fn from_iter<I: IntoIterator<Item = (String, Extern)>>(iter: I) -> Self {
Self {
map: Arc::new(IndexMap::from_iter(iter)),
}
}
}
impl LikeNamespace for Exports {
fn get_namespace_export(&self, name: &str) -> Option<Export> {
self.map.get(name).map(|is_export| is_export.to_export())
}
fn get_namespace_exports(&self) -> Vec<(String, Export)> {
self.map
.iter()
.map(|(k, v)| (k.clone(), v.to_export()))
.collect()
}
}
/// This trait is used to mark types as gettable from an [`Instance`].
///
/// [`Instance`]: crate::js::Instance
pub trait Exportable<'a>: Sized {
/// This function is used when providedd the [`Extern`] as exportable, so it
/// can be used while instantiating the [`Module`].
///
/// [`Module`]: crate::js::Module
fn to_export(&self) -> Export;
/// Implementation of how to get the export corresponding to the implementing type
/// from an [`Instance`] by name.
///
/// [`Instance`]: crate::js::Instance
fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError>;
}
/// A trait for accessing exports (like [`Exportable`]) but it takes generic
/// `Args` and `Rets` parameters so that `NativeFunc` can be accessed directly
/// as well.
pub trait ExportableWithGenerics<'a, Args: WasmTypeList, Rets: WasmTypeList>: Sized {
/// Get an export with the given generics.
fn get_self_from_extern_with_generics(_extern: &'a Extern) -> Result<Self, ExportError>;
}
/// We implement it for all concrete [`Exportable`] types (that are `Clone`)
/// with empty `Args` and `Rets`.
impl<'a, T: Exportable<'a> + Clone + 'static> ExportableWithGenerics<'a, (), ()> for T {
fn get_self_from_extern_with_generics(_extern: &'a Extern) -> Result<Self, ExportError> {
T::get_self_from_extern(_extern).map(|i| i.clone())
}
}

1400
lib/api/src/js/externals/function.rs vendored Normal file

File diff suppressed because it is too large Load Diff

242
lib/api/src/js/externals/global.rs vendored Normal file
View File

@@ -0,0 +1,242 @@
use crate::js::export::Export;
use crate::js::export::VMGlobal;
use crate::js::exports::{ExportError, Exportable};
use crate::js::externals::Extern;
use crate::js::store::Store;
use crate::js::types::{Val, ValType};
use crate::js::wasm_bindgen_polyfill::Global as JSGlobal;
use crate::js::GlobalType;
use crate::js::Mutability;
use crate::js::RuntimeError;
use wasm_bindgen::JsValue;
/// A WebAssembly `global` instance.
///
/// A global instance is the runtime representation of a global variable.
/// It consists of an individual value and a flag indicating whether it is mutable.
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#global-instances>
#[derive(Debug, Clone, PartialEq)]
pub struct Global {
store: Store,
vm_global: VMGlobal,
}
impl Global {
/// Create a new `Global` with the initial value [`Val`].
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Mutability, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// assert_eq!(g.get(), Value::I32(1));
/// assert_eq!(g.ty().mutability, Mutability::Const);
/// ```
pub fn new(store: &Store, val: Val) -> Self {
Self::from_value(store, val, Mutability::Const).unwrap()
}
/// Create a mutable `Global` with the initial value [`Val`].
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Mutability, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new_mut(&store, Value::I32(1));
///
/// assert_eq!(g.get(), Value::I32(1));
/// assert_eq!(g.ty().mutability, Mutability::Var);
/// ```
pub fn new_mut(store: &Store, val: Val) -> Self {
Self::from_value(store, val, Mutability::Var).unwrap()
}
/// Create a `Global` with the initial value [`Val`] and the provided [`Mutability`].
fn from_value(store: &Store, val: Val, mutability: Mutability) -> Result<Self, RuntimeError> {
let global_ty = GlobalType {
mutability,
ty: val.ty(),
};
let descriptor = js_sys::Object::new();
let (type_str, value) = match val {
Val::I32(i) => ("i32", JsValue::from_f64(i as _)),
Val::I64(i) => ("i64", JsValue::from_f64(i as _)),
Val::F32(f) => ("f32", JsValue::from_f64(f as _)),
Val::F64(f) => ("f64", JsValue::from_f64(f)),
_ => unimplemented!("The type is not yet supported in the JS Global API"),
};
// This is the value type as string, even though is incorrectly called "value"
// in the JS API.
js_sys::Reflect::set(&descriptor, &"value".into(), &type_str.into())?;
js_sys::Reflect::set(
&descriptor,
&"mutable".into(),
&mutability.is_mutable().into(),
)?;
let js_global = JSGlobal::new(&descriptor, &value).unwrap();
let global = VMGlobal::new(js_global, global_ty);
Ok(Self {
store: store.clone(),
vm_global: global,
})
}
/// Returns the [`GlobalType`] of the `Global`.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Mutability, Store, Type, Value, GlobalType};
/// # let store = Store::default();
/// #
/// let c = Global::new(&store, Value::I32(1));
/// let v = Global::new_mut(&store, Value::I64(1));
///
/// assert_eq!(c.ty(), &GlobalType::new(Type::I32, Mutability::Const));
/// assert_eq!(v.ty(), &GlobalType::new(Type::I64, Mutability::Var));
/// ```
pub fn ty(&self) -> &GlobalType {
&self.vm_global.ty
}
/// Returns the [`Store`] where the `Global` belongs.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// assert_eq!(g.store(), &store);
/// ```
pub fn store(&self) -> &Store {
&self.store
}
/// Retrieves the current value [`Val`] that the Global has.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// assert_eq!(g.get(), Value::I32(1));
/// ```
pub fn get(&self) -> Val {
match self.vm_global.ty.ty {
ValType::I32 => Val::I32(self.vm_global.global.value().as_f64().unwrap() as _),
ValType::I64 => Val::I64(self.vm_global.global.value().as_f64().unwrap() as _),
ValType::F32 => Val::F32(self.vm_global.global.value().as_f64().unwrap() as _),
ValType::F64 => Val::F64(self.vm_global.global.value().as_f64().unwrap()),
_ => unimplemented!("The type is not yet supported in the JS Global API"),
}
}
/// Sets a custom value [`Val`] to the runtime Global.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new_mut(&store, Value::I32(1));
///
/// assert_eq!(g.get(), Value::I32(1));
///
/// g.set(Value::I32(2));
///
/// assert_eq!(g.get(), Value::I32(2));
/// ```
///
/// # Errors
///
/// Trying to mutate a immutable global will raise an error:
///
/// ```should_panic
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// g.set(Value::I32(2)).unwrap();
/// ```
///
/// Trying to set a value of a incompatible type will raise an error:
///
/// ```should_panic
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// // This results in an error: `RuntimeError`.
/// g.set(Value::I64(2)).unwrap();
/// ```
pub fn set(&self, val: Val) -> Result<(), RuntimeError> {
if self.vm_global.ty.mutability == Mutability::Const {
return Err(RuntimeError::new("The global is immutable".to_owned()));
}
if val.ty() != self.vm_global.ty.ty {
return Err(RuntimeError::new("The types don't match".to_owned()));
}
let new_value = match val {
Val::I32(i) => JsValue::from_f64(i as _),
Val::I64(i) => JsValue::from_f64(i as _),
Val::F32(f) => JsValue::from_f64(f as _),
Val::F64(f) => JsValue::from_f64(f),
_ => unimplemented!("The type is not yet supported in the JS Global API"),
};
self.vm_global.global.set_value(&new_value);
Ok(())
}
pub(crate) fn from_vm_export(store: &Store, vm_global: VMGlobal) -> Self {
Self {
store: store.clone(),
vm_global,
}
}
/// Returns whether or not these two globals refer to the same data.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Global, Store, Value};
/// # let store = Store::default();
/// #
/// let g = Global::new(&store, Value::I32(1));
///
/// assert!(g.same(&g));
/// ```
pub fn same(&self, other: &Self) -> bool {
self.vm_global == other.vm_global
}
}
impl<'a> Exportable<'a> for Global {
fn to_export(&self) -> Export {
Export::Global(self.vm_global.clone())
}
fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> {
match _extern {
Extern::Global(global) => Ok(global),
_ => Err(ExportError::IncompatibleType),
}
}
}

331
lib/api/src/js/externals/memory.rs vendored Normal file
View File

@@ -0,0 +1,331 @@
use crate::js::export::{Export, VMMemory};
use crate::js::exports::{ExportError, Exportable};
use crate::js::externals::Extern;
use crate::js::store::Store;
use crate::js::{MemoryType, MemoryView};
use std::convert::TryInto;
use thiserror::Error;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasmer_types::{Bytes, Pages, ValueType};
/// Error type describing things that can go wrong when operating on Wasm Memories.
#[derive(Error, Debug, Clone, PartialEq, Hash)]
pub enum MemoryError {
/// The operation would cause the size of the memory to exceed the maximum or would cause
/// an overflow leading to unindexable memory.
#[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)]
CouldNotGrow {
/// The current size in pages.
current: Pages,
/// The attempted amount to grow by in pages.
attempted_delta: Pages,
},
/// A user defined error value, used for error cases not listed above.
#[error("A user-defined error occurred: {0}")]
Generic(String),
}
#[wasm_bindgen]
extern "C" {
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory)
#[wasm_bindgen(js_namespace = WebAssembly, extends = js_sys::Object, typescript_type = "WebAssembly.Memory")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JSMemory;
/// The `grow()` protoype method of the `Memory` object increases the
/// size of the memory instance by a specified number of WebAssembly
/// pages.
///
/// Takes the number of pages to grow (64KiB in size) and returns the
/// previous size of memory, in pages.
///
/// # Reimplementation
///
/// We re-implement `WebAssembly.Memory.grow` because it is
/// different from what `wasm-bindgen` declares. It marks the function
/// as `catch`, which means it can throw an exception.
///
/// See [the opened patch](https://github.com/rustwasm/wasm-bindgen/pull/2599).
///
/// # Exceptions
///
/// A `RangeError` is thrown if adding pages would exceed the maximum
/// memory.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow)
#[wasm_bindgen(catch, method, js_namespace = WebAssembly)]
pub fn grow(this: &JSMemory, pages: u32) -> Result<u32, JsValue>;
}
/// A WebAssembly `memory` instance.
///
/// A memory instance is the runtime representation of a linear memory.
/// It consists of a vector of bytes and an optional maximum size.
///
/// The length of the vector always is a multiple of the WebAssembly
/// page size, which is defined to be the constant 65536 abbreviated 64Ki.
/// Like in a memory type, the maximum size in a memory instance is
/// given in units of this page size.
///
/// A memory created by the host or in WebAssembly code will be accessible and
/// mutable from both host and WebAssembly.
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#memory-instances>
#[derive(Debug, Clone, PartialEq)]
pub struct Memory {
store: Store,
vm_memory: VMMemory,
}
impl Memory {
/// Creates a new host `Memory` from the provided [`MemoryType`].
///
/// This function will construct the `Memory` using the store
/// [`BaseTunables`][crate::js::tunables::BaseTunables].
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap();
/// ```
pub fn new(store: &Store, ty: MemoryType) -> Result<Self, MemoryError> {
let descriptor = js_sys::Object::new();
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap();
if let Some(max) = ty.maximum {
js_sys::Reflect::set(&descriptor, &"maximum".into(), &max.0.into()).unwrap();
}
js_sys::Reflect::set(&descriptor, &"shared".into(), &ty.shared.into()).unwrap();
let js_memory = js_sys::WebAssembly::Memory::new(&descriptor)
.map_err(|_e| MemoryError::Generic("Error while creating the memory".to_owned()))?;
let memory = VMMemory::new(js_memory, ty);
Ok(Self {
store: store.clone(),
vm_memory: memory,
})
}
/// Returns the [`MemoryType`] of the `Memory`.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value};
/// # let store = Store::default();
/// #
/// let mt = MemoryType::new(1, None, false);
/// let m = Memory::new(&store, mt).unwrap();
///
/// assert_eq!(m.ty(), mt);
/// ```
pub fn ty(&self) -> MemoryType {
let mut ty = self.vm_memory.ty.clone();
ty.minimum = self.size();
ty
}
/// Returns the [`Store`] where the `Memory` belongs.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap();
///
/// assert_eq!(m.store(), &store);
/// ```
pub fn store(&self) -> &Store {
&self.store
}
/// Retrieve a slice of the memory contents.
///
/// # Safety
///
/// Until the returned slice is dropped, it is undefined behaviour to
/// modify the memory contents in any way including by calling a wasm
/// function that writes to the memory or by resizing the memory.
pub unsafe fn data_unchecked(&self) -> &[u8] {
unimplemented!("direct data pointer access is not possible in JavaScript");
}
/// Retrieve a mutable slice of the memory contents.
///
/// # Safety
///
/// This method provides interior mutability without an UnsafeCell. Until
/// the returned value is dropped, it is undefined behaviour to read or
/// write to the pointed-to memory in any way except through this slice,
/// including by calling a wasm function that reads the memory contents or
/// by resizing this Memory.
#[allow(clippy::mut_from_ref)]
pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] {
unimplemented!("direct data pointer access is not possible in JavaScript");
}
/// Returns the pointer to the raw bytes of the `Memory`.
pub fn data_ptr(&self) -> *mut u8 {
unimplemented!("direct data pointer access is not possible in JavaScript");
}
/// Returns the size (in bytes) of the `Memory`.
pub fn data_size(&self) -> u64 {
js_sys::Reflect::get(&self.vm_memory.memory.buffer(), &"byteLength".into())
.unwrap()
.as_f64()
.unwrap() as _
}
/// Returns the size (in [`Pages`]) of the `Memory`.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap();
///
/// assert_eq!(m.size(), Pages(1));
/// ```
pub fn size(&self) -> Pages {
let bytes = js_sys::Reflect::get(&self.vm_memory.memory.buffer(), &"byteLength".into())
.unwrap()
.as_f64()
.unwrap() as u64;
Bytes(bytes as usize).try_into().unwrap()
}
/// Grow memory by the specified amount of WebAssembly [`Pages`] and return
/// the previous memory size.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, Some(3), false)).unwrap();
/// let p = m.grow(2).unwrap();
///
/// assert_eq!(p, Pages(1));
/// assert_eq!(m.size(), Pages(3));
/// ```
///
/// # Errors
///
/// Returns an error if memory can't be grown by the specified amount
/// of pages.
///
/// ```should_panic
/// # use wasmer_js::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, Some(1), false)).unwrap();
///
/// // This results in an error: `MemoryError::CouldNotGrow`.
/// let s = m.grow(1).unwrap();
/// ```
pub fn grow<IntoPages>(&self, delta: IntoPages) -> Result<Pages, MemoryError>
where
IntoPages: Into<Pages>,
{
let pages = delta.into();
let js_memory = self.vm_memory.memory.clone().unchecked_into::<JSMemory>();
let new_pages = js_memory.grow(pages.0).map_err(|err| {
if err.is_instance_of::<js_sys::RangeError>() {
MemoryError::CouldNotGrow {
current: self.size(),
attempted_delta: pages,
}
} else {
MemoryError::Generic(err.as_string().unwrap())
}
})?;
Ok(Pages(new_pages))
}
/// Return a "view" of the currently accessible memory. By
/// default, the view is unsynchronized, using regular memory
/// accesses. You can force a memory view to use atomic accesses
/// by calling the [`MemoryView::atomically`] method.
///
/// # Notes:
///
/// This method is safe (as in, it won't cause the host to crash or have UB),
/// but it doesn't obey rust's rules involving data races, especially concurrent ones.
/// Therefore, if this memory is shared between multiple threads, a single memory
/// location can be mutated concurrently without synchronization.
///
/// # Usage:
///
/// ```
/// # use wasmer_js::{Memory, MemoryView};
/// # use std::{cell::Cell, sync::atomic::Ordering};
/// # fn view_memory(memory: Memory) {
/// // Without synchronization.
/// let view: MemoryView<u8> = memory.view();
/// for byte in view[0x1000 .. 0x1010].iter().map(Cell::get) {
/// println!("byte: {}", byte);
/// }
///
/// // With synchronization.
/// let atomic_view = view.atomically();
/// for byte in atomic_view[0x1000 .. 0x1010].iter().map(|atom| atom.load(Ordering::SeqCst)) {
/// println!("byte: {}", byte);
/// }
/// # }
/// ```
pub fn view<T: ValueType>(&self) -> MemoryView<T> {
unimplemented!("The view function is not yet implemented in Wasmer Javascript");
}
/// example view
pub fn uint8view(&self) -> js_sys::Uint8Array {
js_sys::Uint8Array::new(&self.vm_memory.memory.buffer())
}
pub(crate) fn from_vm_export(store: &Store, vm_memory: VMMemory) -> Self {
Self {
store: store.clone(),
vm_memory,
}
}
/// Returns whether or not these two memories refer to the same data.
///
/// # Example
///
/// ```
/// # use wasmer_js::{Memory, MemoryType, Store, Value};
/// # let store = Store::default();
/// #
/// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap();
///
/// assert!(m.same(&m));
/// ```
pub fn same(&self, other: &Self) -> bool {
self.vm_memory == other.vm_memory
}
}
impl<'a> Exportable<'a> for Memory {
fn to_export(&self) -> Export {
Export::Memory(self.vm_memory.clone())
}
fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> {
match _extern {
Extern::Memory(memory) => Ok(memory),
_ => Err(ExportError::IncompatibleType),
}
}
}

113
lib/api/src/js/externals/mod.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
pub(crate) mod function;
mod global;
mod memory;
mod table;
pub use self::function::{
FromToNativeWasmType, Function, HostFunction, WasmTypeList, WithEnv, WithoutEnv,
};
pub use self::global::Global;
pub use self::memory::{Memory, MemoryError};
pub use self::table::Table;
use crate::js::export::Export;
use crate::js::exports::{ExportError, Exportable};
use crate::js::store::{Store, StoreObject};
use crate::js::ExternType;
use std::fmt;
/// An `Extern` is the runtime representation of an entity that
/// can be imported or exported.
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#external-values>
#[derive(Clone)]
pub enum Extern {
/// A external [`Function`].
Function(Function),
/// A external [`Global`].
Global(Global),
/// A external [`Table`].
Table(Table),
/// A external [`Memory`].
Memory(Memory),
}
impl Extern {
/// Return the underlying type of the inner `Extern`.
pub fn ty(&self) -> ExternType {
match self {
Self::Function(ft) => ExternType::Function(ft.ty().clone()),
Self::Memory(ft) => ExternType::Memory(ft.ty()),
Self::Table(tt) => ExternType::Table(*tt.ty()),
Self::Global(gt) => ExternType::Global(*gt.ty()),
}
}
/// Create an `Extern` from an `wasmer_engine::Export`.
pub fn from_vm_export(store: &Store, export: Export) -> Self {
match export {
Export::Function(f) => Self::Function(Function::from_vm_export(store, f)),
Export::Memory(m) => Self::Memory(Memory::from_vm_export(store, m)),
Export::Global(g) => Self::Global(Global::from_vm_export(store, g)),
Export::Table(t) => Self::Table(Table::from_vm_export(store, t)),
}
}
}
impl<'a> Exportable<'a> for Extern {
fn to_export(&self) -> Export {
match self {
Self::Function(f) => f.to_export(),
Self::Global(g) => g.to_export(),
Self::Memory(m) => m.to_export(),
Self::Table(t) => t.to_export(),
}
}
fn get_self_from_extern(_extern: &'a Self) -> Result<&'a Self, ExportError> {
// Since this is already an extern, we can just return it.
Ok(_extern)
}
}
impl StoreObject for Extern {}
impl fmt::Debug for Extern {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Function(_) => "Function(...)",
Self::Global(_) => "Global(...)",
Self::Memory(_) => "Memory(...)",
Self::Table(_) => "Table(...)",
}
)
}
}
impl From<Function> for Extern {
fn from(r: Function) -> Self {
Self::Function(r)
}
}
impl From<Global> for Extern {
fn from(r: Global) -> Self {
Self::Global(r)
}
}
impl From<Memory> for Extern {
fn from(r: Memory) -> Self {
Self::Memory(r)
}
}
impl From<Table> for Extern {
fn from(r: Table) -> Self {
Self::Table(r)
}
}

167
lib/api/src/js/externals/table.rs vendored Normal file
View File

@@ -0,0 +1,167 @@
use crate::js::export::VMFunction;
use crate::js::export::{Export, VMTable};
use crate::js::exports::{ExportError, Exportable};
use crate::js::externals::{Extern, Function as WasmerFunction};
use crate::js::store::Store;
use crate::js::types::Val;
use crate::js::RuntimeError;
use crate::js::TableType;
use js_sys::Function;
use wasmer_types::FunctionType;
/// A WebAssembly `table` instance.
///
/// The `Table` struct is an array-like structure representing a WebAssembly Table,
/// which stores function references.
///
/// A table created by the host or in WebAssembly code will be accessible and
/// mutable from both host and WebAssembly.
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#table-instances>
#[derive(Debug, Clone, PartialEq)]
pub struct Table {
store: Store,
vm_table: VMTable,
}
fn set_table_item(table: &VMTable, item_index: u32, item: &Function) -> Result<(), RuntimeError> {
table.table.set(item_index, item).map_err(|e| e.into())
}
fn get_function(val: Val) -> Result<Function, RuntimeError> {
match val {
Val::FuncRef(func) => Ok(func.as_ref().unwrap().exported.function.clone().into()),
// Only funcrefs is supported by the spec atm
_ => unimplemented!(),
}
}
impl Table {
/// Creates a new `Table` with the provided [`TableType`] definition.
///
/// All the elements in the table will be set to the `init` value.
///
/// This function will construct the `Table` using the store
/// [`BaseTunables`][crate::js::tunables::BaseTunables].
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Self, RuntimeError> {
let descriptor = js_sys::Object::new();
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.into())?;
if let Some(max) = ty.maximum {
js_sys::Reflect::set(&descriptor, &"maximum".into(), &max.into())?;
}
js_sys::Reflect::set(&descriptor, &"element".into(), &"anyfunc".into())?;
let js_table = js_sys::WebAssembly::Table::new(&descriptor)?;
let table = VMTable::new(js_table, ty);
let num_elements = table.table.length();
let func = get_function(init)?;
for i in 0..num_elements {
set_table_item(&table, i, &func)?;
}
Ok(Self {
store: store.clone(),
vm_table: table,
})
}
/// Returns the [`TableType`] of the `Table`.
pub fn ty(&self) -> &TableType {
&self.vm_table.ty
}
/// Returns the [`Store`] where the `Table` belongs.
pub fn store(&self) -> &Store {
&self.store
}
/// Retrieves an element of the table at the provided `index`.
pub fn get(&self, index: u32) -> Option<Val> {
let func = self.vm_table.table.get(index).ok()?;
let ty = FunctionType::new(vec![], vec![]);
Some(Val::FuncRef(Some(WasmerFunction::from_vm_export(
&self.store,
VMFunction::new(func, ty, None),
))))
}
/// Sets an element `val` in the Table at the provided `index`.
pub fn set(&self, index: u32, val: Val) -> Result<(), RuntimeError> {
let func = get_function(val)?;
set_table_item(&self.vm_table, index, &func)?;
Ok(())
}
/// Retrieves the size of the `Table` (in elements)
pub fn size(&self) -> u32 {
self.vm_table.table.length()
}
/// Grows the size of the `Table` by `delta`, initializating
/// the elements with the provided `init` value.
///
/// It returns the previous size of the `Table` in case is able
/// to grow the Table successfully.
///
/// # Errors
///
/// Returns an error if the `delta` is out of bounds for the table.
pub fn grow(&self, _delta: u32, _init: Val) -> Result<u32, RuntimeError> {
unimplemented!();
}
/// Copies the `len` elements of `src_table` starting at `src_index`
/// to the destination table `dst_table` at index `dst_index`.
///
/// # Errors
///
/// Returns an error if the range is out of bounds of either the source or
/// destination tables.
pub fn copy(
_dst_table: &Self,
_dst_index: u32,
_src_table: &Self,
_src_index: u32,
_len: u32,
) -> Result<(), RuntimeError> {
unimplemented!("Table.copy is not natively supported in Javascript");
}
pub(crate) fn from_vm_export(store: &Store, vm_table: VMTable) -> Self {
Self {
store: store.clone(),
vm_table,
}
}
/// Returns whether or not these two tables refer to the same data.
pub fn same(&self, other: &Self) -> bool {
self.vm_table == other.vm_table
}
/// Get access to the backing VM value for this extern. This function is for
/// tests it should not be called by users of the Wasmer API.
///
/// # Safety
/// This function is unsafe to call outside of tests for the wasmer crate
/// because there is no stability guarantee for the returned type and we may
/// make breaking changes to it at any time or remove this method.
#[doc(hidden)]
pub unsafe fn get_vm_table(&self) -> &VMTable {
&self.vm_table
}
}
impl<'a> Exportable<'a> for Table {
fn to_export(&self) -> Export {
Export::Table(self.vm_table.clone())
}
fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> {
match _extern {
Extern::Table(table) => Ok(table),
_ => Err(ExportError::IncompatibleType),
}
}
}

View File

@@ -0,0 +1,412 @@
//! The import module contains the implementation data structures and helper functions used to
//! manipulate and access a wasm module's imports including memories, tables, globals, and
//! functions.
use crate::js::export::Export;
use crate::js::resolver::NamedResolver;
use std::borrow::{Borrow, BorrowMut};
use std::collections::VecDeque;
use std::collections::{hash_map::Entry, HashMap};
use std::fmt;
use std::sync::{Arc, Mutex};
/// The `LikeNamespace` trait represents objects that act as a namespace for imports.
/// For example, an `Instance` or `Namespace` could be
/// considered namespaces that could provide imports to an instance.
pub trait LikeNamespace {
/// Gets an export by name.
fn get_namespace_export(&self, name: &str) -> Option<Export>;
/// Gets all exports in the namespace.
fn get_namespace_exports(&self) -> Vec<(String, Export)>;
}
/// All of the import data used when instantiating.
///
/// It's suggested that you use the [`imports!`] macro
/// instead of creating an `ImportObject` by hand.
///
/// [`imports!`]: macro.imports.html
///
/// # Usage:
/// ```ignore
/// use wasmer_js::{Exports, ImportObject, Function};
///
/// let mut import_object = ImportObject::new();
/// let mut env = Exports::new();
///
/// env.insert("foo", Function::new_native(foo));
/// import_object.register("env", env);
///
/// fn foo(n: i32) -> i32 {
/// n
/// }
/// ```
#[derive(Clone, Default)]
pub struct ImportObject {
map: Arc<Mutex<HashMap<String, Box<dyn LikeNamespace>>>>,
}
impl ImportObject {
/// Create a new `ImportObject`.
pub fn new() -> Self {
Default::default()
}
/// Gets an export given a module and a name
///
/// # Usage
/// ```ignore
/// # use wasmer_js::{ImportObject, Instance, Namespace};
/// let mut import_object = ImportObject::new();
/// import_object.get_export("module", "name");
/// ```
pub fn get_export(&self, module: &str, name: &str) -> Option<Export> {
let guard = self.map.lock().unwrap();
let map_ref = guard.borrow();
if map_ref.contains_key(module) {
let namespace = map_ref[module].as_ref();
return namespace.get_namespace_export(name);
}
None
}
/// Returns true if the ImportObject contains namespace with the provided name.
pub fn contains_namespace(&self, name: &str) -> bool {
self.map.lock().unwrap().borrow().contains_key(name)
}
/// Register anything that implements `LikeNamespace` as a namespace.
///
/// # Usage:
/// ```ignore
/// # use wasmer_js::{ImportObject, Instance, Namespace};
/// let mut import_object = ImportObject::new();
///
/// import_object.register("namespace0", instance);
/// import_object.register("namespace1", namespace);
/// // ...
/// ```
pub fn register<S, N>(&mut self, name: S, namespace: N) -> Option<Box<dyn LikeNamespace>>
where
S: Into<String>,
N: LikeNamespace + 'static,
{
let mut guard = self.map.lock().unwrap();
let map = guard.borrow_mut();
match map.entry(name.into()) {
Entry::Vacant(empty) => {
empty.insert(Box::new(namespace));
None
}
Entry::Occupied(mut occupied) => Some(occupied.insert(Box::new(namespace))),
}
}
fn get_objects(&self) -> VecDeque<((String, String), Export)> {
let mut out = VecDeque::new();
let guard = self.map.lock().unwrap();
let map = guard.borrow();
for (name, ns) in map.iter() {
for (id, exp) in ns.get_namespace_exports() {
out.push_back(((name.clone(), id), exp));
}
}
out
}
}
impl NamedResolver for ImportObject {
fn resolve_by_name(&self, module: &str, name: &str) -> Option<Export> {
self.get_export(module, name)
}
}
/// Iterator for an `ImportObject`'s exports.
pub struct ImportObjectIterator {
elements: VecDeque<((String, String), Export)>,
}
impl Iterator for ImportObjectIterator {
type Item = ((String, String), Export);
fn next(&mut self) -> Option<Self::Item> {
self.elements.pop_front()
}
}
impl IntoIterator for ImportObject {
type IntoIter = ImportObjectIterator;
type Item = ((String, String), Export);
fn into_iter(self) -> Self::IntoIter {
ImportObjectIterator {
elements: self.get_objects(),
}
}
}
impl fmt::Debug for ImportObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
enum SecretMap {
Empty,
Some(usize),
}
impl SecretMap {
fn new(len: usize) -> Self {
if len == 0 {
Self::Empty
} else {
Self::Some(len)
}
}
}
impl fmt::Debug for SecretMap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Empty => write!(f, "(empty)"),
Self::Some(len) => write!(f, "(... {} item(s) ...)", len),
}
}
}
f.debug_struct("ImportObject")
.field(
"map",
&SecretMap::new(self.map.lock().unwrap().borrow().len()),
)
.finish()
}
}
// The import! macro for ImportObject
/// Generate an [`ImportObject`] easily with the `imports!` macro.
///
/// [`ImportObject`]: struct.ImportObject.html
///
/// # Usage
///
/// ```
/// # use wasmer_js::{Function, Store};
/// # let store = Store::default();
/// use wasmer_js::imports;
///
/// let import_object = imports! {
/// "env" => {
/// "foo" => Function::new_native(&store, foo)
/// },
/// };
///
/// fn foo(n: i32) -> i32 {
/// n
/// }
/// ```
#[macro_export]
macro_rules! imports {
( $( $ns_name:expr => $ns:tt ),* $(,)? ) => {
{
let mut import_object = $crate::ImportObject::new();
$({
let namespace = $crate::import_namespace!($ns);
import_object.register($ns_name, namespace);
})*
import_object
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! namespace {
($( $import_name:expr => $import_item:expr ),* $(,)? ) => {
$crate::import_namespace!( { $( $import_name => $import_item, )* } )
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! import_namespace {
( { $( $import_name:expr => $import_item:expr ),* $(,)? } ) => {{
let mut namespace = $crate::Exports::new();
$(
namespace.insert($import_name, $import_item);
)*
namespace
}};
( $namespace:ident ) => {
$namespace
};
}
#[cfg(test)]
mod test {
use super::*;
use crate::js::ChainableNamedResolver;
use crate::js::Type;
use crate::js::{Global, Store, Val};
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn chaining_works() {
let store = Store::default();
let g = Global::new(&store, Val::I32(0));
let imports1 = imports! {
"dog" => {
"happy" => g.clone()
}
};
let imports2 = imports! {
"dog" => {
"small" => g.clone()
},
"cat" => {
"small" => g.clone()
}
};
let resolver = imports1.chain_front(imports2);
let small_cat_export = resolver.resolve_by_name("cat", "small");
assert!(small_cat_export.is_some());
let happy = resolver.resolve_by_name("dog", "happy");
let small = resolver.resolve_by_name("dog", "small");
assert!(happy.is_some());
assert!(small.is_some());
}
#[wasm_bindgen_test]
fn extending_conflict_overwrites() {
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
let resolver = imports1.chain_front(imports2);
let happy_dog_entry = resolver.resolve_by_name("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::F32
} else {
false
});
// now test it in reverse
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
let resolver = imports1.chain_back(imports2);
let happy_dog_entry = resolver.resolve_by_name("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::I32
} else {
false
});
}
#[wasm_bindgen_test]
fn namespace() {
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let namespace = namespace! {
"happy" => g1
};
let imports1 = imports! {
"dog" => namespace
};
let happy_dog_entry = imports1.resolve_by_name("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::I32
} else {
false
});
}
#[wasm_bindgen_test]
fn imports_macro_allows_trailing_comma_and_none() {
use crate::js::Function;
let store = Default::default();
fn func(arg: i32) -> i32 {
arg + 1
}
let _ = imports! {
"env" => {
"func" => Function::new_native(&store, func),
},
};
let _ = imports! {
"env" => {
"func" => Function::new_native(&store, func),
}
};
let _ = imports! {
"env" => {
"func" => Function::new_native(&store, func),
},
"abc" => {
"def" => Function::new_native(&store, func),
}
};
let _ = imports! {
"env" => {
"func" => Function::new_native(&store, func)
},
};
let _ = imports! {
"env" => {
"func" => Function::new_native(&store, func)
}
};
let _ = imports! {
"env" => {
"func1" => Function::new_native(&store, func),
"func2" => Function::new_native(&store, func)
}
};
let _ = imports! {
"env" => {
"func1" => Function::new_native(&store, func),
"func2" => Function::new_native(&store, func),
}
};
}
}

142
lib/api/src/js/instance.rs Normal file
View File

@@ -0,0 +1,142 @@
use crate::js::env::HostEnvInitError;
use crate::js::export::Export;
use crate::js::exports::Exports;
use crate::js::externals::Extern;
use crate::js::module::Module;
use crate::js::resolver::Resolver;
use crate::js::store::Store;
use crate::js::trap::RuntimeError;
use js_sys::WebAssembly;
use std::fmt;
#[cfg(feature = "std")]
use thiserror::Error;
/// A WebAssembly Instance is a stateful, executable
/// instance of a WebAssembly [`Module`].
///
/// Instance objects contain all the exported WebAssembly
/// functions, memories, tables and globals that allow
/// interacting with WebAssembly.
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#module-instances>
#[derive(Clone)]
pub struct Instance {
instance: WebAssembly::Instance,
module: Module,
/// The exports for an instance.
pub exports: Exports,
}
/// 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), a
/// Trap that occurs when calling the WebAssembly module
/// start function, and an error when initializing the user's
/// host environments.
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
pub enum InstantiationError {
/// A linking ocurred during instantiation.
#[cfg_attr(feature = "std", error("Link error: {0}"))]
Link(String),
/// A runtime error occured while invoking the start function
#[cfg_attr(feature = "std", error(transparent))]
Start(RuntimeError),
/// Error occurred when initializing the host environment.
#[cfg_attr(feature = "std", error(transparent))]
HostEnvInitialization(HostEnvInitError),
}
#[cfg(feature = "core")]
impl std::fmt::Display for InstantiationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InstantiationError")
}
}
impl Instance {
/// Creates a new `Instance` from a WebAssembly [`Module`] and a
/// set of imports resolved by the [`Resolver`].
///
/// The resolver can be anything that implements the [`Resolver`] trait,
/// so you can plug custom resolution for the imports, if you wish not
/// to use [`ImportObject`].
///
/// The [`ImportObject`] is the easiest way to provide imports to the instance.
///
/// [`ImportObject`]: crate::js::ImportObject
///
/// ```
/// # use wasmer_js::{imports, Store, Module, Global, Value, Instance};
/// # fn main() -> anyhow::Result<()> {
/// let store = Store::default();
/// let module = Module::new(&store, "(module)")?;
/// let imports = imports!{
/// "host" => {
/// "var" => Global::new(&store, Value::I32(2))
/// }
/// };
/// let instance = Instance::new(&module, &imports)?;
/// # Ok(())
/// # }
/// ```
///
/// ## Errors
///
/// The function can return [`InstantiationError`]s.
///
/// Those are, as defined by the spec:
/// * Link errors that happen when plugging the imports into the instance
/// * Runtime errors that happen when running the module `start` function.
pub fn new(module: &Module, resolver: &dyn Resolver) -> Result<Self, InstantiationError> {
let store = module.store();
let (instance, functions) = module
.instantiate(resolver)
.map_err(|e| InstantiationError::Start(e))?;
let instance_exports = instance.exports();
let exports = module
.exports()
.map(|export_type| {
let name = export_type.name();
let extern_type = export_type.ty().clone();
let js_export = js_sys::Reflect::get(&instance_exports, &name.into()).unwrap();
let export: Export = (js_export, extern_type).into();
let extern_ = Extern::from_vm_export(store, export);
(name.to_string(), extern_)
})
.collect::<Exports>();
let self_instance = Self {
module: module.clone(),
instance,
exports,
};
for func in functions {
func.init_envs(&self_instance)
.map_err(|e| InstantiationError::HostEnvInitialization(e))?;
}
Ok(self_instance)
}
/// Gets the [`Module`] associated with this instance.
pub fn module(&self) -> &Module {
&self.module
}
/// Returns the [`Store`] where the `Instance` belongs.
pub fn store(&self) -> &Store {
self.module.store()
}
}
impl fmt::Debug for Instance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Instance")
.field("exports", &self.exports)
.finish()
}
}

144
lib/api/src/js/mod.rs Normal file
View File

@@ -0,0 +1,144 @@
#![doc(
html_logo_url = "https://github.com/wasmerio.png?size=200",
html_favicon_url = "https://wasmer.io/images/icons/favicon-32x32.png"
)]
#![deny(
missing_docs,
trivial_numeric_casts,
unused_extern_crates,
broken_intra_doc_links
)]
#![warn(unused_import_braces)]
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::new_without_default, clippy::vtable_address_comparisons)
)]
#![cfg_attr(
feature = "cargo-clippy",
warn(
clippy::float_arithmetic,
clippy::mut_mut,
clippy::nonminimal_bool,
clippy::map_unwrap_or,
clippy::print_stdout,
clippy::unicode_not_nfc,
clippy::use_self
)
)]
//! This crate contains the `wasmer-js` API. The `wasmer-js` API facilitates the efficient,
//! sandboxed execution of [WebAssembly (Wasm)][wasm] modules, leveraging on the same
//! API as the `wasmer` crate, but targeting Javascript.
//!
//! This crate uses the same WebAssembly engine as the Javascript VM where it's used.
//!
//! Here's an example of the `wasmer-js` API in action:
//! ```
//! #[wasm_bindgen]
//! pub extern fn do_add_one_in_wasmer() -> i32 {
//! let module_wat = r#"
//! (module
//! (type $t0 (func (param i32) (result i32)))
//! (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
//! get_local $p0
//! i32.const 1
//! i32.add))
//! "#;
//! let store = Store::default();
//! let module = Module::new(&store, &module_wat).unwrap();
//! // The module doesn't import anything, so we create an empty import object.
//! let import_object = imports! {};
//! let instance = Instance::new(&module, &import_object).unwrap();
//! let add_one = instance.exports.get_function("add_one").unwrap();
//! let result = add_one.call(&[Value::I32(42)]).unwrap();
//! assert_eq!(result[0], Value::I32(43));
//! result[0].unwrap_i32()
//! }
//! ```
//!
//! For more examples of using the `wasmer` API, check out the
//! [wasmer examples][wasmer-examples].
#[cfg(all(feature = "std", feature = "core"))]
compile_error!(
"The `std` and `core` features are both enabled, which is an error. Please enable only once."
);
#[cfg(all(not(feature = "std"), not(feature = "core")))]
compile_error!("Both the `std` and `core` features are disabled. Please enable one of them.");
#[cfg(feature = "core")]
extern crate alloc;
mod lib {
#[cfg(feature = "core")]
pub mod std {
pub use alloc::{borrow, boxed, str, string, sync, vec};
pub use core::fmt;
pub use hashbrown as collections;
}
#[cfg(feature = "std")]
pub mod std {
pub use std::{borrow, boxed, collections, fmt, str, string, sync, vec};
}
}
mod cell;
mod env;
mod error;
mod export;
mod exports;
mod externals;
mod import_object;
mod instance;
mod module;
#[cfg(feature = "wasm-types-polyfill")]
mod module_info_polyfill;
mod native;
mod ptr;
mod resolver;
mod store;
mod trap;
mod types;
mod utils;
mod wasm_bindgen_polyfill;
/// Implement [`WasmerEnv`] for your type with `#[derive(WasmerEnv)]`.
///
/// See the [`WasmerEnv`] trait for more information.
pub use wasmer_derive::WasmerEnv;
pub use crate::js::cell::WasmCell;
pub use crate::js::env::{HostEnvInitError, LazyInit, WasmerEnv};
pub use crate::js::exports::{ExportError, Exportable, Exports, ExportsIterator};
pub use crate::js::externals::{
Extern, FromToNativeWasmType, Function, Global, HostFunction, Memory, MemoryError, Table,
WasmTypeList,
};
pub use crate::js::import_object::{ImportObject, ImportObjectIterator, LikeNamespace};
pub use crate::js::instance::{Instance, InstantiationError};
pub use crate::js::module::{Module, ModuleTypeHints};
pub use crate::js::native::NativeFunc;
pub use crate::js::ptr::{Array, Item, WasmPtr};
pub use crate::js::resolver::{ChainableNamedResolver, NamedResolver, NamedResolverChain, Resolver};
pub use crate::js::trap::RuntimeError;
pub use crate::js::store::{Store, StoreObject};
pub use crate::js::types::{
ExportType, ExternType, FunctionType, GlobalType, ImportType, MemoryType, Mutability,
TableType, Val, ValType,
};
pub use crate::js::types::{Val as Value, ValType as Type};
pub use crate::js::utils::is_wasm;
pub use wasmer_types::{
Atomically, Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, MemoryView, Pages, ValueType,
WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE,
};
#[cfg(feature = "wat")]
pub use wat::parse_bytes as wat2wasm;
/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

519
lib/api/src/js/module.rs Normal file
View File

@@ -0,0 +1,519 @@
use crate::js::export::{Export, VMFunction};
use crate::js::resolver::Resolver;
use crate::js::store::Store;
use crate::js::types::{ExportType, ImportType};
// use crate::js::InstantiationError;
use crate::js::error::CompileError;
#[cfg(feature = "wat")]
use crate::js::error::WasmError;
use crate::js::RuntimeError;
use js_sys::{Reflect, Uint8Array, WebAssembly};
use std::fmt;
use std::io;
use std::path::Path;
#[cfg(feature = "std")]
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasmer_types::{
ExportsIterator, ExternType, FunctionType, GlobalType, ImportsIterator, MemoryType, Mutability,
Pages, TableType, Type,
};
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
pub enum IoCompileError {
/// An IO error
#[cfg_attr(feature = "std", error(transparent))]
Io(io::Error),
/// A compilation error
#[cfg_attr(feature = "std", error(transparent))]
Compile(CompileError),
}
/// WebAssembly in the browser doesn't yet output the descriptor/types
/// corresponding to each extern (import and export).
///
/// This should be fixed once the JS-Types Wasm proposal is adopted
/// by the browsers:
/// https://github.com/WebAssembly/js-types/blob/master/proposals/js-types/Overview.md
///
/// Until that happens, we annotate the module with the expected
/// types so we can built on top of them at runtime.
#[derive(Clone)]
pub struct ModuleTypeHints {
/// The type hints for the imported types
pub imports: Vec<ExternType>,
/// The type hints for the exported types
pub exports: Vec<ExternType>,
}
/// A WebAssembly Module contains stateless WebAssembly
/// code that has already been compiled and can be instantiated
/// multiple times.
///
/// ## Cloning a module
///
/// Cloning a module is cheap: it does a shallow copy of the compiled
/// contents rather than a deep copy.
#[derive(Clone)]
pub struct Module {
store: Store,
module: WebAssembly::Module,
name: Option<String>,
// WebAssembly type hints
type_hints: Option<ModuleTypeHints>,
}
impl Module {
/// Creates a new WebAssembly Module given the configuration
/// in the store.
///
/// If the provided bytes are not WebAssembly-like (start with `b"\0asm"`),
/// and the "wat" feature is enabled for this crate, this function will try to
/// to convert the bytes assuming they correspond to the WebAssembly text
/// format.
///
/// ## Security
///
/// Before the code is compiled, it will be validated using the store
/// features.
///
/// ## Errors
///
/// Creating a WebAssembly module from bytecode can result in a
/// [`CompileError`] since this operation requires to transorm the Wasm
/// bytecode into code the machine can easily execute.
///
/// ## Example
///
/// Reading from a WAT file.
///
/// ```
/// use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = "(module)";
/// let module = Module::new(&store, wat)?;
/// # Ok(())
/// # }
/// ```
///
/// Reading from bytes:
///
/// ```
/// use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// // The following is the same as:
/// // (module
/// // (type $t0 (func (param i32) (result i32)))
/// // (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
/// // get_local $p0
/// // i32.const 1
/// // i32.add)
/// // )
/// let bytes: Vec<u8> = vec![
/// 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60,
/// 0x01, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0b, 0x01, 0x07,
/// 0x61, 0x64, 0x64, 0x5f, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x0a, 0x09, 0x01,
/// 0x07, 0x00, 0x20, 0x00, 0x41, 0x01, 0x6a, 0x0b, 0x00, 0x1a, 0x04, 0x6e,
/// 0x61, 0x6d, 0x65, 0x01, 0x0a, 0x01, 0x00, 0x07, 0x61, 0x64, 0x64, 0x5f,
/// 0x6f, 0x6e, 0x65, 0x02, 0x07, 0x01, 0x00, 0x01, 0x00, 0x02, 0x70, 0x30,
/// ];
/// let module = Module::new(&store, bytes)?;
/// # Ok(())
/// # }
/// ```
#[allow(unreachable_code)]
pub fn new(store: &Store, bytes: impl AsRef<[u8]>) -> Result<Self, CompileError> {
#[cfg(feature = "wat")]
let bytes = wat::parse_bytes(bytes.as_ref()).map_err(|e| {
CompileError::Wasm(WasmError::Generic(format!(
"Error when converting wat: {}",
e
)))
})?;
Self::from_binary(store, bytes.as_ref())
}
/// Creates a new WebAssembly module from a file path.
pub fn from_file(_store: &Store, _file: impl AsRef<Path>) -> Result<Self, IoCompileError> {
unimplemented!();
}
/// Creates a new WebAssembly module from a binary.
///
/// Opposed to [`Module::new`], this function is not compatible with
/// the WebAssembly text format (if the "wat" feature is enabled for
/// this crate).
pub fn from_binary(store: &Store, binary: &[u8]) -> Result<Self, CompileError> {
//
// Self::validate(store, binary)?;
unsafe { Self::from_binary_unchecked(store, binary) }
}
/// Creates a new WebAssembly module skipping any kind of validation.
///
/// # Safety
///
/// This is safe since the JS vm should be safe already.
/// We maintain the `unsafe` to preserve the same API as Wasmer
pub unsafe fn from_binary_unchecked(
store: &Store,
binary: &[u8],
) -> Result<Self, CompileError> {
let js_bytes = Uint8Array::view(binary);
let module = WebAssembly::Module::new(&js_bytes.into()).unwrap();
// The module is now validated, so we can safely parse it's types
#[cfg(feature = "wasm-types-polyfill")]
let (type_hints, name) = {
let info = crate::js::module_info_polyfill::translate_module(binary).unwrap();
(
Some(ModuleTypeHints {
imports: info
.info
.imports()
.map(|import| import.ty().clone())
.collect::<Vec<_>>(),
exports: info
.info
.exports()
.map(|export| export.ty().clone())
.collect::<Vec<_>>(),
}),
info.info.name,
)
};
#[cfg(not(feature = "wasm-types-polyfill"))]
let (type_hints, name) = (None, None);
Ok(Self {
store: store.clone(),
module,
type_hints,
name,
})
}
/// Validates a new WebAssembly Module given the configuration
/// in the Store.
///
/// This validation is normally pretty fast and checks the enabled
/// WebAssembly features in the Store Engine to assure deterministic
/// validation of the Module.
pub fn validate(_store: &Store, binary: &[u8]) -> Result<(), CompileError> {
let js_bytes = unsafe { Uint8Array::view(binary) };
match WebAssembly::validate(&js_bytes.into()) {
Ok(true) => Ok(()),
_ => Err(CompileError::Validate("Invalid Wasm file".to_owned())),
}
}
pub(crate) fn instantiate(
&self,
resolver: &dyn Resolver,
) -> Result<(WebAssembly::Instance, Vec<VMFunction>), RuntimeError> {
let imports = js_sys::Object::new();
let mut functions: Vec<VMFunction> = vec![];
for (i, import_type) in self.imports().enumerate() {
let resolved_import =
resolver.resolve(i as u32, import_type.module(), import_type.name());
if let Some(import) = resolved_import {
let val = js_sys::Reflect::get(&imports, &import_type.module().into())?;
if !val.is_undefined() {
// If the namespace is already set
js_sys::Reflect::set(&val, &import_type.name().into(), import.as_jsvalue())?;
} else {
// If the namespace doesn't exist
let import_namespace = js_sys::Object::new();
js_sys::Reflect::set(
&import_namespace,
&import_type.name().into(),
import.as_jsvalue(),
)?;
js_sys::Reflect::set(
&imports,
&import_type.module().into(),
&import_namespace.into(),
)?;
}
if let Export::Function(func) = import {
functions.push(func);
}
}
// in case the import is not found, the JS Wasm VM will handle
// the error for us, so we don't need to handle it
}
Ok((
WebAssembly::Instance::new(&self.module, &imports)
.map_err(|e: JsValue| -> RuntimeError { e.into() })?,
functions,
))
}
/// Returns the name of the current module.
///
/// This name is normally set in the WebAssembly bytecode by some
/// compilers, but can be also overwritten using the [`Module::set_name`] method.
///
/// # Example
///
/// ```
/// # use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = "(module $moduleName)";
/// let module = Module::new(&store, wat)?;
/// assert_eq!(module.name(), Some("moduleName"));
/// # Ok(())
/// # }
/// ```
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(|s| s.as_ref())
// self.artifact.module_ref().name.as_deref()
}
/// Sets the name of the current module.
/// This is normally useful for stacktraces and debugging.
///
/// It will return `true` if the module name was changed successfully,
/// and return `false` otherwise (in case the module is already
/// instantiated).
///
/// # Example
///
/// ```
/// # use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = "(module)";
/// let mut module = Module::new(&store, wat)?;
/// assert_eq!(module.name(), None);
/// module.set_name("foo");
/// assert_eq!(module.name(), Some("foo"));
/// # Ok(())
/// # }
/// ```
pub fn set_name(&mut self, name: &str) -> bool {
self.name = Some(name.to_string());
true
// match Reflect::set(self.module.as_ref(), &"wasmer_name".into(), &name.into()) {
// Ok(_) => true,
// _ => false
// }
// Arc::get_mut(&mut self.artifact)
// .and_then(|artifact| artifact.module_mut())
// .map(|mut module_info| {
// module_info.info.name = Some(name.to_string());
// true
// })
// .unwrap_or(false)
}
/// Returns an iterator over the imported types in the Module.
///
/// The order of the imports is guaranteed to be the same as in the
/// WebAssembly bytecode.
///
/// # Example
///
/// ```
/// # use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = r#"(module
/// (import "host" "func1" (func))
/// (import "host" "func2" (func))
/// )"#;
/// let module = Module::new(&store, wat)?;
/// for import in module.imports() {
/// assert_eq!(import.module(), "host");
/// assert!(import.name().contains("func"));
/// import.ty();
/// }
/// # Ok(())
/// # }
/// ```
pub fn imports<'a>(&'a self) -> ImportsIterator<impl Iterator<Item = ImportType> + 'a> {
let imports = WebAssembly::Module::imports(&self.module);
let iter = imports
.iter()
.map(move |val| {
let module = Reflect::get(val.as_ref(), &"module".into())
.unwrap()
.as_string()
.unwrap();
let field = Reflect::get(val.as_ref(), &"name".into())
.unwrap()
.as_string()
.unwrap();
let kind = Reflect::get(val.as_ref(), &"kind".into())
.unwrap()
.as_string()
.unwrap();
let extern_type = match kind.as_str() {
"function" => {
let func_type = FunctionType::new(vec![], vec![]);
ExternType::Function(func_type)
}
"global" => {
let global_type = GlobalType::new(Type::I32, Mutability::Const);
ExternType::Global(global_type)
}
"memory" => {
let memory_type = MemoryType::new(Pages(1), None, false);
ExternType::Memory(memory_type)
}
"table" => {
let table_type = TableType::new(Type::FuncRef, 1, None);
ExternType::Table(table_type)
}
_ => unimplemented!(),
};
ImportType::new(&module, &field, extern_type)
})
.collect::<Vec<_>>()
.into_iter();
ImportsIterator::new(iter, imports.length() as usize)
}
/// Set the type hints for this module.
///
/// Returns an error if the hints doesn't match the shape of
/// import or export types of the module.
pub fn set_type_hints(&mut self, type_hints: ModuleTypeHints) -> Result<(), String> {
let exports = WebAssembly::Module::exports(&self.module);
// Check exports
if exports.length() as usize != type_hints.exports.len() {
return Err("The exports length must match the type hints lenght".to_owned());
}
for (i, val) in exports.iter().enumerate() {
let kind = Reflect::get(val.as_ref(), &"kind".into())
.unwrap()
.as_string()
.unwrap();
// It is safe to unwrap as we have already checked for the exports length
let type_hint = type_hints.exports.get(i).unwrap();
let expected_kind = match type_hint {
ExternType::Function(_) => "function",
ExternType::Global(_) => "global",
ExternType::Memory(_) => "memory",
ExternType::Table(_) => "table",
};
if expected_kind != kind.as_str() {
return Err(format!("The provided type hint for the export {} is {} which doesn't match the expected kind: {}", i, kind.as_str(), expected_kind));
}
}
self.type_hints = Some(type_hints);
Ok(())
}
/// Returns an iterator over the exported types in the Module.
///
/// The order of the exports is guaranteed to be the same as in the
/// WebAssembly bytecode.
///
/// # Example
///
/// ```
/// # use wasmer_js::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = r#"(module
/// (func (export "namedfunc"))
/// (memory (export "namedmemory") 1)
/// )"#;
/// let module = Module::new(&store, wat)?;
/// for export_ in module.exports() {
/// assert!(export_.name().contains("named"));
/// export_.ty();
/// }
/// # Ok(())
/// # }
/// ```
pub fn exports<'a>(&'a self) -> ExportsIterator<impl Iterator<Item = ExportType> + 'a> {
let exports = WebAssembly::Module::exports(&self.module);
let iter = exports
.iter()
.enumerate()
.map(move |(i, val)| {
let field = Reflect::get(val.as_ref(), &"name".into())
.unwrap()
.as_string()
.unwrap();
let kind = Reflect::get(val.as_ref(), &"kind".into())
.unwrap()
.as_string()
.unwrap();
let type_hint = self
.type_hints
.as_ref()
.map(|hints| hints.exports.get(i).unwrap().clone());
let extern_type = if let Some(hint) = type_hint {
hint
} else {
// The default types
match kind.as_str() {
"function" => {
let func_type = FunctionType::new(vec![], vec![]);
ExternType::Function(func_type)
}
"global" => {
let global_type = GlobalType::new(Type::I32, Mutability::Const);
ExternType::Global(global_type)
}
"memory" => {
let memory_type = MemoryType::new(Pages(1), None, false);
ExternType::Memory(memory_type)
}
"table" => {
let table_type = TableType::new(Type::FuncRef, 1, None);
ExternType::Table(table_type)
}
_ => unimplemented!(),
}
};
ExportType::new(&field, extern_type)
})
.collect::<Vec<_>>()
.into_iter();
ExportsIterator::new(iter, exports.length() as usize)
}
// /// Get the custom sections of the module given a `name`.
// ///
// /// # Important
// ///
// /// Following the WebAssembly spec, one name can have multiple
// /// custom sections. That's why an iterator (rather than one element)
// /// is returned.
// pub fn custom_sections<'a>(&'a self, name: &'a str) -> impl Iterator<Item = Arc<[u8]>> + 'a {
// unimplemented!();
// }
/// Returns the [`Store`] where the `Instance` belongs.
pub fn store(&self) -> &Store {
&self.store
}
}
impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Module")
.field("name", &self.name())
.finish()
}
}
impl From<WebAssembly::Module> for Module {
fn from(module: WebAssembly::Module) -> Module {
Module {
store: Store::default(),
module,
name: None,
type_hints: None,
}
}
}

View File

@@ -0,0 +1,600 @@
//! Polyfill skeleton that traverses the whole WebAssembly module and
//! creates the corresponding import and export types.
//!
//! This shall not be needed once the JS type reflection API is available
//! for the Wasm imports and exports.
//!
//! https://github.com/WebAssembly/js-types/blob/master/proposals/js-types/Overview.md
use core::convert::TryFrom;
use std::vec::Vec;
use wasmer_types::entity::EntityRef;
use wasmer_types::{
ExportIndex, FunctionIndex, FunctionType, GlobalIndex, GlobalType, ImportIndex, MemoryIndex,
MemoryType, ModuleInfo, Pages, SignatureIndex, TableIndex, TableType, Type,
};
use wasmparser::{
self, BinaryReaderError, Export, ExportSectionReader, ExternalKind, FuncType as WPFunctionType,
FunctionSectionReader, GlobalSectionReader, GlobalType as WPGlobalType, ImportSectionEntryType,
ImportSectionReader, MemorySectionReader, MemoryType as WPMemoryType, NameSectionReader,
Parser, Payload, TableSectionReader, TypeDef, TypeSectionReader,
};
pub type WasmResult<T> = Result<T, String>;
#[derive(Default)]
pub struct ModuleInfoPolyfill {
pub(crate) info: ModuleInfo,
}
impl ModuleInfoPolyfill {
pub(crate) fn declare_export(&mut self, export: ExportIndex, name: &str) -> WasmResult<()> {
self.info.exports.insert(String::from(name), export);
Ok(())
}
pub(crate) fn declare_import(
&mut self,
import: ImportIndex,
module: &str,
field: &str,
) -> WasmResult<()> {
self.info.imports.insert(
(
String::from(module),
String::from(field),
self.info.imports.len() as u32,
),
import,
);
Ok(())
}
pub(crate) fn reserve_signatures(&mut self, num: u32) -> WasmResult<()> {
self.info
.signatures
.reserve_exact(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_signature(&mut self, sig: FunctionType) -> WasmResult<()> {
self.info.signatures.push(sig);
Ok(())
}
pub(crate) fn declare_func_import(
&mut self,
sig_index: SignatureIndex,
module: &str,
field: &str,
) -> WasmResult<()> {
debug_assert_eq!(
self.info.functions.len(),
self.info.num_imported_functions,
"Imported functions must be declared first"
);
self.declare_import(
ImportIndex::Function(FunctionIndex::from_u32(
self.info.num_imported_functions as _,
)),
module,
field,
)?;
self.info.functions.push(sig_index);
self.info.num_imported_functions += 1;
Ok(())
}
pub(crate) fn declare_table_import(
&mut self,
table: TableType,
module: &str,
field: &str,
) -> WasmResult<()> {
debug_assert_eq!(
self.info.tables.len(),
self.info.num_imported_tables,
"Imported tables must be declared first"
);
self.declare_import(
ImportIndex::Table(TableIndex::from_u32(self.info.num_imported_tables as _)),
module,
field,
)?;
self.info.tables.push(table);
self.info.num_imported_tables += 1;
Ok(())
}
pub(crate) fn declare_memory_import(
&mut self,
memory: MemoryType,
module: &str,
field: &str,
) -> WasmResult<()> {
debug_assert_eq!(
self.info.memories.len(),
self.info.num_imported_memories,
"Imported memories must be declared first"
);
self.declare_import(
ImportIndex::Memory(MemoryIndex::from_u32(self.info.num_imported_memories as _)),
module,
field,
)?;
self.info.memories.push(memory);
self.info.num_imported_memories += 1;
Ok(())
}
pub(crate) fn declare_global_import(
&mut self,
global: GlobalType,
module: &str,
field: &str,
) -> WasmResult<()> {
debug_assert_eq!(
self.info.globals.len(),
self.info.num_imported_globals,
"Imported globals must be declared first"
);
self.declare_import(
ImportIndex::Global(GlobalIndex::from_u32(self.info.num_imported_globals as _)),
module,
field,
)?;
self.info.globals.push(global);
self.info.num_imported_globals += 1;
Ok(())
}
pub(crate) fn reserve_func_types(&mut self, num: u32) -> WasmResult<()> {
self.info
.functions
.reserve_exact(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_func_type(&mut self, sig_index: SignatureIndex) -> WasmResult<()> {
self.info.functions.push(sig_index);
Ok(())
}
pub(crate) fn reserve_tables(&mut self, num: u32) -> WasmResult<()> {
self.info
.tables
.reserve_exact(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_table(&mut self, table: TableType) -> WasmResult<()> {
self.info.tables.push(table);
Ok(())
}
pub(crate) fn reserve_memories(&mut self, num: u32) -> WasmResult<()> {
self.info
.memories
.reserve_exact(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_memory(&mut self, memory: MemoryType) -> WasmResult<()> {
self.info.memories.push(memory);
Ok(())
}
pub(crate) fn reserve_globals(&mut self, num: u32) -> WasmResult<()> {
self.info
.globals
.reserve_exact(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_global(&mut self, global: GlobalType) -> WasmResult<()> {
self.info.globals.push(global);
Ok(())
}
pub(crate) fn reserve_exports(&mut self, num: u32) -> WasmResult<()> {
self.info.exports.reserve(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn reserve_imports(&mut self, num: u32) -> WasmResult<()> {
self.info.imports.reserve(usize::try_from(num).unwrap());
Ok(())
}
pub(crate) fn declare_func_export(
&mut self,
func_index: FunctionIndex,
name: &str,
) -> WasmResult<()> {
self.declare_export(ExportIndex::Function(func_index), name)
}
pub(crate) fn declare_table_export(
&mut self,
table_index: TableIndex,
name: &str,
) -> WasmResult<()> {
self.declare_export(ExportIndex::Table(table_index), name)
}
pub(crate) fn declare_memory_export(
&mut self,
memory_index: MemoryIndex,
name: &str,
) -> WasmResult<()> {
self.declare_export(ExportIndex::Memory(memory_index), name)
}
pub(crate) fn declare_global_export(
&mut self,
global_index: GlobalIndex,
name: &str,
) -> WasmResult<()> {
self.declare_export(ExportIndex::Global(global_index), name)
}
pub(crate) fn declare_module_name(&mut self, name: &str) -> WasmResult<()> {
self.info.name = Some(name.to_string());
Ok(())
}
}
fn transform_err(err: BinaryReaderError) -> String {
err.message().into()
}
/// Translate a sequence of bytes forming a valid Wasm binary into a
/// parsed ModuleInfo `ModuleInfoPolyfill`.
pub fn translate_module<'data>(data: &'data [u8]) -> WasmResult<ModuleInfoPolyfill> {
let mut module_info: ModuleInfoPolyfill = Default::default();
for payload in Parser::new(0).parse_all(data) {
match payload.map_err(transform_err)? {
Payload::TypeSection(types) => {
parse_type_section(types, &mut module_info)?;
}
Payload::ImportSection(imports) => {
parse_import_section(imports, &mut module_info)?;
}
Payload::FunctionSection(functions) => {
parse_function_section(functions, &mut module_info)?;
}
Payload::TableSection(tables) => {
parse_table_section(tables, &mut module_info)?;
}
Payload::MemorySection(memories) => {
parse_memory_section(memories, &mut module_info)?;
}
Payload::GlobalSection(globals) => {
parse_global_section(globals, &mut module_info)?;
}
Payload::ExportSection(exports) => {
parse_export_section(exports, &mut module_info)?;
}
Payload::CustomSection {
name: "name",
data,
data_offset,
..
} => parse_name_section(
NameSectionReader::new(data, data_offset).map_err(transform_err)?,
&mut module_info,
)?,
_ => {}
}
}
Ok(module_info)
}
/// Helper function translating wasmparser types to Wasm Type.
pub fn wptype_to_type(ty: wasmparser::Type) -> WasmResult<Type> {
match ty {
wasmparser::Type::I32 => Ok(Type::I32),
wasmparser::Type::I64 => Ok(Type::I64),
wasmparser::Type::F32 => Ok(Type::F32),
wasmparser::Type::F64 => Ok(Type::F64),
wasmparser::Type::V128 => Ok(Type::V128),
wasmparser::Type::ExternRef => Ok(Type::ExternRef),
wasmparser::Type::FuncRef => Ok(Type::FuncRef),
ty => Err(format!("wptype_to_type: wasmparser type {:?}", ty)),
}
}
/// Parses the Type section of the wasm module.
pub fn parse_type_section(
types: TypeSectionReader,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
let count = types.get_count();
module_info.reserve_signatures(count)?;
for entry in types {
if let Ok(TypeDef::Func(WPFunctionType { params, returns })) = entry {
let sig_params: Vec<Type> = params
.iter()
.map(|ty| {
wptype_to_type(*ty)
.expect("only numeric types are supported in function signatures")
})
.collect();
let sig_returns: Vec<Type> = returns
.iter()
.map(|ty| {
wptype_to_type(*ty)
.expect("only numeric types are supported in function signatures")
})
.collect();
let sig = FunctionType::new(sig_params, sig_returns);
module_info.declare_signature(sig)?;
} else {
unimplemented!("module linking not implemented yet")
}
}
Ok(())
}
/// Parses the Import section of the wasm module.
pub fn parse_import_section<'data>(
imports: ImportSectionReader<'data>,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
module_info.reserve_imports(imports.get_count())?;
for entry in imports {
let import = entry.map_err(transform_err)?;
let module_name = import.module;
let field_name = import.field;
match import.ty {
ImportSectionEntryType::Function(sig) => {
module_info.declare_func_import(
SignatureIndex::from_u32(sig),
module_name,
field_name.unwrap_or_default(),
)?;
}
ImportSectionEntryType::Module(_)
| ImportSectionEntryType::Instance(_)
| ImportSectionEntryType::Event(_) => {
unimplemented!("module linking not implemented yet")
}
ImportSectionEntryType::Memory(WPMemoryType::M32 {
limits: ref memlimits,
shared,
}) => {
module_info.declare_memory_import(
MemoryType {
minimum: Pages(memlimits.initial),
maximum: memlimits.maximum.map(Pages),
shared,
},
module_name,
field_name.unwrap_or_default(),
)?;
}
ImportSectionEntryType::Memory(WPMemoryType::M64 { .. }) => {
unimplemented!("64bit memory not implemented yet")
}
ImportSectionEntryType::Global(ref ty) => {
module_info.declare_global_import(
GlobalType {
ty: wptype_to_type(ty.content_type).unwrap(),
mutability: ty.mutable.into(),
},
module_name,
field_name.unwrap_or_default(),
)?;
}
ImportSectionEntryType::Table(ref tab) => {
module_info.declare_table_import(
TableType {
ty: wptype_to_type(tab.element_type).unwrap(),
minimum: tab.limits.initial,
maximum: tab.limits.maximum,
},
module_name,
field_name.unwrap_or_default(),
)?;
}
}
}
Ok(())
}
/// Parses the Function section of the wasm module.
pub fn parse_function_section(
functions: FunctionSectionReader,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
let num_functions = functions.get_count();
module_info.reserve_func_types(num_functions)?;
for entry in functions {
let sigindex = entry.map_err(transform_err)?;
module_info.declare_func_type(SignatureIndex::from_u32(sigindex))?;
}
Ok(())
}
/// Parses the Table section of the wasm module.
pub fn parse_table_section(
tables: TableSectionReader,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
module_info.reserve_tables(tables.get_count())?;
for entry in tables {
let table = entry.map_err(transform_err)?;
module_info.declare_table(TableType {
ty: wptype_to_type(table.element_type).unwrap(),
minimum: table.limits.initial,
maximum: table.limits.maximum,
})?;
}
Ok(())
}
/// Parses the Memory section of the wasm module.
pub fn parse_memory_section(
memories: MemorySectionReader,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
module_info.reserve_memories(memories.get_count())?;
for entry in memories {
let memory = entry.map_err(transform_err)?;
match memory {
WPMemoryType::M32 { limits, shared } => {
module_info.declare_memory(MemoryType {
minimum: Pages(limits.initial),
maximum: limits.maximum.map(Pages),
shared,
})?;
}
WPMemoryType::M64 { .. } => unimplemented!("64bit memory not implemented yet"),
}
}
Ok(())
}
/// Parses the Global section of the wasm module.
pub fn parse_global_section(
globals: GlobalSectionReader,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
module_info.reserve_globals(globals.get_count())?;
for entry in globals {
let WPGlobalType {
content_type,
mutable,
} = entry.map_err(transform_err)?.ty;
let global = GlobalType {
ty: wptype_to_type(content_type).unwrap(),
mutability: mutable.into(),
};
module_info.declare_global(global)?;
}
Ok(())
}
/// Parses the Export section of the wasm module.
pub fn parse_export_section<'data>(
exports: ExportSectionReader<'data>,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
module_info.reserve_exports(exports.get_count())?;
for entry in exports {
let Export {
field,
ref kind,
index,
} = entry.map_err(transform_err)?;
// The input has already been validated, so we should be able to
// assume valid UTF-8 and use `from_utf8_unchecked` if performance
// becomes a concern here.
let index = index as usize;
match *kind {
ExternalKind::Function => {
module_info.declare_func_export(FunctionIndex::new(index), field)?
}
ExternalKind::Table => {
module_info.declare_table_export(TableIndex::new(index), field)?
}
ExternalKind::Memory => {
module_info.declare_memory_export(MemoryIndex::new(index), field)?
}
ExternalKind::Global => {
module_info.declare_global_export(GlobalIndex::new(index), field)?
}
ExternalKind::Type
| ExternalKind::Module
| ExternalKind::Instance
| ExternalKind::Event => {
unimplemented!("module linking not implemented yet")
}
}
}
Ok(())
}
// /// Parses the Start section of the wasm module.
// pub fn parse_start_section(index: u32, module_info: &mut ModuleInfoPolyfill) -> WasmResult<()> {
// module_info.declare_start_function(FunctionIndex::from_u32(index))?;
// Ok(())
// }
/// Parses the Name section of the wasm module.
pub fn parse_name_section<'data>(
mut names: NameSectionReader<'data>,
module_info: &mut ModuleInfoPolyfill,
) -> WasmResult<()> {
while let Ok(subsection) = names.read() {
match subsection {
wasmparser::Name::Function(_function_subsection) => {
// if let Some(function_names) = function_subsection
// .get_map()
// .ok()
// .and_then(parse_function_name_subsection)
// {
// for (index, name) in function_names {
// module_info.declare_function_name(index, name)?;
// }
// }
}
wasmparser::Name::Module(module) => {
if let Ok(name) = module.get_name() {
module_info.declare_module_name(name)?;
}
}
wasmparser::Name::Local(_) => {}
wasmparser::Name::Unknown { .. } => {}
};
}
Ok(())
}
// fn parse_function_name_subsection(
// mut naming_reader: NamingReader<'_>,
// ) -> Option<HashMap<FunctionIndex, &str>> {
// let mut function_names = HashMap::new();
// for _ in 0..naming_reader.get_count() {
// let Naming { index, name } = naming_reader.read().ok()?;
// if index == std::u32::MAX {
// // We reserve `u32::MAX` for our own use.
// return None;
// }
// if function_names
// .insert(FunctionIndex::from_u32(index), name)
// .is_some()
// {
// // If the function index has been previously seen, then we
// // break out of the loop and early return `None`, because these
// // should be unique.
// return None;
// }
// }
// Some(function_names)
// }

149
lib/api/src/js/native.rs Normal file
View File

@@ -0,0 +1,149 @@
//! Native Functions.
//!
//! This module creates the helper `NativeFunc` that let us call WebAssembly
//! functions with the native ABI, that is:
//!
//! ```ignore
//! let add_one = instance.exports.get_function("function_name")?;
//! let add_one_native: NativeFunc<i32, i32> = add_one.native().unwrap();
//! ```
use std::marker::PhantomData;
use crate::js::{FromToNativeWasmType, Function, RuntimeError, Store, WasmTypeList};
// use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::js::export::VMFunction;
use crate::js::types::param_from_js;
use js_sys::Array;
use std::iter::FromIterator;
use wasm_bindgen::JsValue;
use wasmer_types::NativeWasmType;
/// A WebAssembly function that can be called natively
/// (using the Native ABI).
#[derive(Clone)]
pub struct NativeFunc<Args = (), Rets = ()> {
store: Store,
exported: VMFunction,
_phantom: PhantomData<(Args, Rets)>,
}
unsafe impl<Args, Rets> Send for NativeFunc<Args, Rets> {}
impl<Args, Rets> NativeFunc<Args, Rets>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub(crate) fn new(store: Store, exported: VMFunction) -> Self {
Self {
store,
exported,
_phantom: PhantomData,
}
}
}
impl<Args, Rets> From<&NativeFunc<Args, Rets>> for VMFunction
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
fn from(other: &NativeFunc<Args, Rets>) -> Self {
other.exported.clone()
}
}
impl<Args, Rets> From<NativeFunc<Args, Rets>> for Function
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
fn from(other: NativeFunc<Args, Rets>) -> Self {
Self {
store: other.store,
exported: other.exported,
}
}
}
macro_rules! impl_native_traits {
( $( $x:ident ),* ) => {
#[allow(unused_parens, non_snake_case)]
impl<$( $x , )* Rets> NativeFunc<( $( $x ),* ), Rets>
where
$( $x: FromToNativeWasmType, )*
Rets: WasmTypeList,
{
/// Call the typed func and return results.
pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
let params_list: Vec<JsValue> = vec![ $( JsValue::from_f64($x.to_native().to_binary() as f64) ),* ];
let results = self.exported.function.apply(
&JsValue::UNDEFINED,
&Array::from_iter(params_list.iter())
)?;
let mut rets_list_array = Rets::empty_array();
let mut_rets = rets_list_array.as_mut() as *mut [i128] as *mut i128;
match Rets::size() {
0 => {},
1 => unsafe {
let ty = Rets::wasm_types()[0];
let val = param_from_js(&ty, &results);
val.write_value_to(mut_rets);
}
_n => {
let results: Array = results.into();
for (i, ret_type) in Rets::wasm_types().iter().enumerate() {
let ret = results.get(i as u32);
unsafe {
let val = param_from_js(&ret_type, &ret);
val.write_value_to(mut_rets.add(i));
}
}
}
}
Ok(Rets::from_array(rets_list_array))
}
}
#[allow(unused_parens)]
impl<'a, $( $x, )* Rets> crate::js::exports::ExportableWithGenerics<'a, ($( $x ),*), Rets> for NativeFunc<( $( $x ),* ), Rets>
where
$( $x: FromToNativeWasmType, )*
Rets: WasmTypeList,
{
fn get_self_from_extern_with_generics(_extern: &crate::js::externals::Extern) -> Result<Self, crate::js::exports::ExportError> {
use crate::js::exports::Exportable;
crate::js::Function::get_self_from_extern(_extern)?.native().map_err(|_| crate::js::exports::ExportError::IncompatibleType)
}
}
};
}
impl_native_traits!();
impl_native_traits!(A1);
impl_native_traits!(A1, A2);
impl_native_traits!(A1, A2, A3);
impl_native_traits!(A1, A2, A3, A4);
impl_native_traits!(A1, A2, A3, A4, A5);
impl_native_traits!(A1, A2, A3, A4, A5, A6);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17);
impl_native_traits!(
A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18
);
impl_native_traits!(
A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19
);
impl_native_traits!(
A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20
);

351
lib/api/src/js/ptr.rs Normal file
View File

@@ -0,0 +1,351 @@
//! Types for a reusable pointer abstraction for accessing Wasm linear memory.
//!
//! This abstraction is safe: it ensures the memory is in bounds and that the pointer
//! is aligned (avoiding undefined behavior).
//!
//! Therefore, you should use this abstraction whenever possible to avoid memory
//! related bugs when implementing an ABI.
use crate::js::cell::WasmCell;
use crate::js::{externals::Memory, FromToNativeWasmType};
use std::{fmt, marker::PhantomData, mem};
use wasmer_types::ValueType;
/// The `Array` marker type. This type can be used like `WasmPtr<T, Array>`
/// to get access to methods
pub struct Array;
/// The `Item` marker type. This is the default and does not usually need to be
/// specified.
pub struct Item;
/// A zero-cost type that represents a pointer to something in Wasm linear
/// memory.
///
/// This type can be used directly in the host function arguments:
/// ```
/// # use wasmer_js::Memory;
/// # use wasmer_js::WasmPtr;
/// pub fn host_import(memory: Memory, ptr: WasmPtr<u32>) {
/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds");
/// let inner_val: u32 = derefed_ptr.get();
/// println!("Got {} from Wasm memory address 0x{:X}", inner_val, ptr.offset());
/// // update the value being pointed to
/// derefed_ptr.set(inner_val + 1);
/// }
/// ```
///
/// This type can also be used with primitive-filled structs, but be careful of
/// guarantees required by `ValueType`.
/// ```
/// # use wasmer_js::Memory;
/// # use wasmer_js::WasmPtr;
/// # use wasmer_js::ValueType;
///
/// #[derive(Copy, Clone, Debug)]
/// #[repr(C)]
/// struct V3 {
/// x: f32,
/// y: f32,
/// z: f32
/// }
/// // This is safe as the 12 bytes represented by this struct
/// // are valid for all bit combinations.
/// unsafe impl ValueType for V3 {
/// }
///
/// fn update_vector_3(memory: Memory, ptr: WasmPtr<V3>) {
/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds");
/// let mut inner_val: V3 = derefed_ptr.get();
/// println!("Got {:?} from Wasm memory address 0x{:X}", inner_val, ptr.offset());
/// // update the value being pointed to
/// inner_val.x = 10.4;
/// derefed_ptr.set(inner_val);
/// }
/// ```
#[repr(transparent)]
pub struct WasmPtr<T: Copy, Ty = Item> {
offset: u32,
_phantom: PhantomData<(T, Ty)>,
}
/// Methods relevant to all types of `WasmPtr`.
impl<T: Copy, Ty> WasmPtr<T, Ty> {
/// Create a new `WasmPtr` at the given offset.
#[inline]
pub fn new(offset: u32) -> Self {
Self {
offset,
_phantom: PhantomData,
}
}
/// Get the offset into Wasm linear memory for this `WasmPtr`.
#[inline]
pub fn offset(self) -> u32 {
self.offset
}
}
/// Methods for `WasmPtr`s to data that can be dereferenced, namely to types
/// that implement [`ValueType`], meaning that they're valid for all possible
/// bit patterns.
impl<T: Copy + ValueType> WasmPtr<T, Item> {
/// Dereference the `WasmPtr` getting access to a `&Cell<T>` allowing for
/// reading and mutating of the inner value.
///
/// This method is unsound if used with unsynchronized shared memory.
/// If you're unsure what that means, it likely does not apply to you.
/// This invariant will be enforced in the future.
#[inline]
pub fn deref<'a>(self, memory: &'a Memory) -> Option<WasmCell<T>> {
let total_len = (self.offset as usize) + mem::size_of::<T>();
if total_len > memory.size().bytes().0 || mem::size_of::<T>() == 0 {
return None;
}
let subarray = memory.uint8view().subarray(self.offset, total_len as u32);
Some(WasmCell::new(subarray))
}
}
/// Methods for `WasmPtr`s to arrays of data that can be dereferenced, namely to
/// types that implement [`ValueType`], meaning that they're valid for all
/// possible bit patterns.
impl<T: Copy + ValueType> WasmPtr<T, Array> {
/// Dereference the `WasmPtr` getting access to a `&[Cell<T>]` allowing for
/// reading and mutating of the inner values.
///
/// This method is unsound if used with unsynchronized shared memory.
/// If you're unsure what that means, it likely does not apply to you.
/// This invariant will be enforced in the future.
#[inline]
pub fn deref(self, memory: &Memory, index: u32, length: u32) -> Option<Vec<WasmCell<T>>> {
// gets the size of the item in the array with padding added such that
// for any index, we will always result an aligned memory access
let item_size = mem::size_of::<T>() as u32;
let slice_full_len = index + length;
let memory_size = memory.size().bytes().0 as u32;
if self.offset + (item_size * slice_full_len) > memory_size
|| self.offset >= memory_size
|| item_size == 0
{
return None;
}
Some(
(0..length)
.map(|i| {
let subarray = memory.uint8view().subarray(
self.offset + i * item_size,
self.offset + (i + 1) * item_size,
);
WasmCell::new(subarray)
})
.collect::<Vec<_>>(),
)
}
/// Get a UTF-8 string from the `WasmPtr` with the given length.
///
/// Note that . The
/// underlying data can be mutated if the Wasm is allowed to execute or
/// an aliasing `WasmPtr` is used to mutate memory.
///
/// # Safety
/// This method returns a reference to Wasm linear memory. The underlying
/// data can be mutated if the Wasm is allowed to execute or an aliasing
/// `WasmPtr` is used to mutate memory.
///
/// `str` has invariants that must not be broken by mutating Wasm memory.
/// Thus the caller must ensure that the backing memory is not modified
/// while the reference is held.
///
/// Additionally, if `memory` is dynamic, the caller must also ensure that `memory`
/// is not grown while the reference is held.
pub unsafe fn get_utf8_str<'a>(
self,
memory: &'a Memory,
str_len: u32,
) -> Option<std::borrow::Cow<'a, str>> {
self.get_utf8_string(memory, str_len)
.map(std::borrow::Cow::from)
}
/// Get a UTF-8 `String` from the `WasmPtr` with the given length.
///
/// an aliasing `WasmPtr` is used to mutate memory.
pub fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option<String> {
let memory_size = memory.size().bytes().0;
if self.offset as usize + str_len as usize > memory.size().bytes().0
|| self.offset as usize >= memory_size
{
return None;
}
let view = memory.uint8view();
// let subarray_as_vec = view.subarray(self.offset, str_len + 1).to_vec();
let mut subarray_as_vec: Vec<u8> = Vec::with_capacity(str_len as usize);
let base = self.offset;
for i in 0..(str_len) {
let byte = view.get_index(base + i);
subarray_as_vec.push(byte);
}
String::from_utf8(subarray_as_vec).ok()
}
}
unsafe impl<T: Copy, Ty> FromToNativeWasmType for WasmPtr<T, Ty> {
type Native = i32;
fn to_native(self) -> Self::Native {
self.offset as i32
}
fn from_native(n: Self::Native) -> Self {
Self {
offset: n as u32,
_phantom: PhantomData,
}
}
}
unsafe impl<T: Copy, Ty> ValueType for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> Clone for WasmPtr<T, Ty> {
fn clone(&self) -> Self {
Self {
offset: self.offset,
_phantom: PhantomData,
}
}
}
impl<T: Copy, Ty> Copy for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> PartialEq for WasmPtr<T, Ty> {
fn eq(&self, other: &Self) -> bool {
self.offset == other.offset
}
}
impl<T: Copy, Ty> Eq for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> fmt::Debug for WasmPtr<T, Ty> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"WasmPtr(offset: {}, pointer: {:#x}, align: {})",
self.offset,
self.offset,
mem::align_of::<T>()
)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::js::{Memory, MemoryType, Store};
use wasm_bindgen_test::*;
/// Ensure that memory accesses work on the edges of memory and that out of
/// bounds errors are caught with both `deref` and `deref_mut`.
#[wasm_bindgen_test]
fn wasm_ptr_is_functional() {
let store = Store::default();
let memory_descriptor = MemoryType::new(1, Some(1), false);
let memory = Memory::new(&store, memory_descriptor).unwrap();
let start_wasm_ptr: WasmPtr<u64> = WasmPtr::new(2);
let val = start_wasm_ptr.deref(&memory).unwrap();
assert_eq!(val.memory.to_vec(), vec![0; 8]);
val.set(1200);
assert_eq!(val.memory.to_vec(), vec![176, 4, 0, 0, 0, 0, 0, 0]);
// Let's make sure the main memory is changed
assert_eq!(
memory.uint8view().subarray(0, 10).to_vec(),
vec![0, 0, 176, 4, 0, 0, 0, 0, 0, 0]
);
val.memory.copy_from(&[10, 0, 0, 0, 0, 0, 0, 0]);
let value = val.get();
assert_eq!(value, 10);
}
/// Ensure that memory accesses work on the edges of memory and that out of
/// bounds errors are caught with both `deref` and `deref_mut`.
#[wasm_bindgen_test]
fn wasm_ptr_memory_bounds_checks_hold() {
// create a memory
let store = Store::default();
let memory_descriptor = MemoryType::new(1, Some(1), false);
let memory = Memory::new(&store, memory_descriptor).unwrap();
// test that basic access works and that len = 0 works, but oob does not
let start_wasm_ptr: WasmPtr<u8> = WasmPtr::new(0);
let start_wasm_ptr_array: WasmPtr<u8, Array> = WasmPtr::new(0);
assert!(start_wasm_ptr.deref(&memory).is_some());
assert!(start_wasm_ptr_array.deref(&memory, 0, 0).is_some());
assert!(unsafe { start_wasm_ptr_array.get_utf8_str(&memory, 0).is_some() });
assert!(start_wasm_ptr_array.get_utf8_string(&memory, 0).is_some());
assert!(start_wasm_ptr_array.deref(&memory, 0, 1).is_some());
// test that accessing the last valid memory address works correctly and OOB is caught
let last_valid_address_for_u8 = (memory.size().bytes().0 - 1) as u32;
let end_wasm_ptr: WasmPtr<u8> = WasmPtr::new(last_valid_address_for_u8);
assert!(end_wasm_ptr.deref(&memory).is_some());
let end_wasm_ptr_array: WasmPtr<u8, Array> = WasmPtr::new(last_valid_address_for_u8);
assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some());
let invalid_idx_len_combos: [(u32, u32); 3] =
[(last_valid_address_for_u8 + 1, 0), (0, 2), (1, 1)];
for &(idx, len) in invalid_idx_len_combos.iter() {
assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none());
}
assert!(unsafe { end_wasm_ptr_array.get_utf8_str(&memory, 2).is_none() });
assert!(end_wasm_ptr_array.get_utf8_string(&memory, 2).is_none());
// test that accesing the last valid memory address for a u32 is valid
// (same as above test but with more edge cases to assert on)
let last_valid_address_for_u32 = (memory.size().bytes().0 - 4) as u32;
let end_wasm_ptr: WasmPtr<u32> = WasmPtr::new(last_valid_address_for_u32);
assert!(end_wasm_ptr.deref(&memory).is_some());
assert!(end_wasm_ptr.deref(&memory).is_some());
let end_wasm_ptr_oob_array: [WasmPtr<u32>; 4] = [
WasmPtr::new(last_valid_address_for_u32 + 1),
WasmPtr::new(last_valid_address_for_u32 + 2),
WasmPtr::new(last_valid_address_for_u32 + 3),
WasmPtr::new(last_valid_address_for_u32 + 4),
];
for oob_end_ptr in end_wasm_ptr_oob_array.iter() {
assert!(oob_end_ptr.deref(&memory).is_none());
}
let end_wasm_ptr_array: WasmPtr<u32, Array> = WasmPtr::new(last_valid_address_for_u32);
assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some());
let invalid_idx_len_combos: [(u32, u32); 3] =
[(last_valid_address_for_u32 + 1, 0), (0, 2), (1, 1)];
for &(idx, len) in invalid_idx_len_combos.iter() {
assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none());
}
let end_wasm_ptr_array_oob_array: [WasmPtr<u32, Array>; 4] = [
WasmPtr::new(last_valid_address_for_u32 + 1),
WasmPtr::new(last_valid_address_for_u32 + 2),
WasmPtr::new(last_valid_address_for_u32 + 3),
WasmPtr::new(last_valid_address_for_u32 + 4),
];
for oob_end_array_ptr in end_wasm_ptr_array_oob_array.iter() {
assert!(oob_end_array_ptr.deref(&memory, 0, 1).is_none());
assert!(oob_end_array_ptr.deref(&memory, 1, 0).is_none());
}
}
}

165
lib/api/src/js/resolver.rs Normal file
View File

@@ -0,0 +1,165 @@
use crate::js::export::Export;
/// Import resolver connects imports with available exported values.
pub trait Resolver {
/// Resolves an import a WebAssembly module to an export it's hooked up to.
///
/// The `index` provided is the index of the import in the wasm module
/// that's being resolved. For example 1 means that it's the second import
/// listed in the wasm module.
///
/// The `module` and `field` arguments provided are the module/field names
/// listed on the import itself.
///
/// # Notes:
///
/// The index is useful because some WebAssembly modules may rely on that
/// for resolving ambiguity in their imports. Such as:
/// ```ignore
/// (module
/// (import "" "" (func))
/// (import "" "" (func (param i32) (result i32)))
/// )
/// ```
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export>;
}
/// Import resolver connects imports with available exported values.
///
/// This is a specific subtrait for [`Resolver`] for those users who don't
/// care about the `index`, but only about the `module` and `field` for
/// the resolution.
pub trait NamedResolver {
/// Resolves an import a WebAssembly module to an export it's hooked up to.
///
/// It receives the `module` and `field` names and return the [`Export`] in
/// case it's found.
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export>;
}
// All NamedResolvers should extend `Resolver`.
impl<T: NamedResolver> Resolver for T {
/// By default this method will be calling [`NamedResolver::resolve_by_name`],
/// dismissing the provided `index`.
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export> {
self.resolve_by_name(module, field)
}
}
impl<T: NamedResolver> NamedResolver for &T {
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
(**self).resolve_by_name(module, field)
}
}
impl NamedResolver for Box<dyn NamedResolver> {
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
(**self).resolve_by_name(module, field)
}
}
impl NamedResolver for () {
/// Always returns `None`.
fn resolve_by_name(&self, _module: &str, _field: &str) -> Option<Export> {
None
}
}
/// `Resolver` implementation that always resolves to `None`. Equivalent to `()`.
pub struct NullResolver {}
impl Resolver for NullResolver {
fn resolve(&self, _idx: u32, _module: &str, _field: &str) -> Option<Export> {
None
}
}
/// A [`Resolver`] that links two resolvers together in a chain.
pub struct NamedResolverChain<A: NamedResolver, B: NamedResolver> {
a: A,
b: B,
}
/// A trait for chaining resolvers together.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized,
/// # B: NamedResolver + Sized,
/// # {
/// // override duplicates with imports from `imports2`
/// imports1.chain_front(imports2);
/// # }
/// ```
pub trait ChainableNamedResolver: NamedResolver + Sized {
/// Chain a resolver in front of the current resolver.
///
/// This will cause the second resolver to override the first.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized,
/// # B: NamedResolver + Sized,
/// # {
/// // override duplicates with imports from `imports2`
/// imports1.chain_front(imports2);
/// # }
/// ```
fn chain_front<U>(self, other: U) -> NamedResolverChain<U, Self>
where
U: NamedResolver,
{
NamedResolverChain { a: other, b: self }
}
/// Chain a resolver behind the current resolver.
///
/// This will cause the first resolver to override the second.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized,
/// # B: NamedResolver + Sized,
/// # {
/// // override duplicates with imports from `imports1`
/// imports1.chain_back(imports2);
/// # }
/// ```
fn chain_back<U>(self, other: U) -> NamedResolverChain<Self, U>
where
U: NamedResolver,
{
NamedResolverChain { a: self, b: other }
}
}
// We give these chain methods to all types implementing NamedResolver
impl<T: NamedResolver> ChainableNamedResolver for T {}
impl<A, B> NamedResolver for NamedResolverChain<A, B>
where
A: NamedResolver,
B: NamedResolver,
{
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
self.a
.resolve_by_name(module, field)
.or_else(|| self.b.resolve_by_name(module, field))
}
}
impl<A, B> Clone for NamedResolverChain<A, B>
where
A: NamedResolver + Clone,
B: NamedResolver + Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
}
}
}

59
lib/api/src/js/store.rs Normal file
View File

@@ -0,0 +1,59 @@
use std::fmt;
/// The store represents all global state that can be manipulated by
/// WebAssembly programs. It consists of the runtime representation
/// of all instances of functions, tables, memories, and globals that
/// have been allocated during the lifetime of the abstract machine.
///
/// The `Store` holds the engine (that is —amongst many things— used to compile
/// the Wasm bytes into a valid module artifact), in addition to the
/// [`Tunables`] (that are used to create the memories, tables and globals).
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#store>
#[derive(Clone)]
pub struct Store;
impl Store {
/// Creates a new `Store`.
pub fn new() -> Self {
Self
}
/// Checks whether two stores are identical. A store is considered
/// equal to another store if both have the same engine. The
/// tunables are excluded from the logic.
pub fn same(_a: &Self, _b: &Self) -> bool {
true
}
}
impl PartialEq for Store {
fn eq(&self, other: &Self) -> bool {
Self::same(self, other)
}
}
// This is required to be able to set the trap_handler in the
// Store.
unsafe impl Send for Store {}
unsafe impl Sync for Store {}
impl Default for Store {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Store").finish()
}
}
/// A trait represinting any object that lives in the `Store`.
pub trait StoreObject {
/// Return true if the object `Store` is the same as the provided `Store`.
fn comes_from_same_store(&self, _store: &Store) -> bool {
true
}
}

163
lib/api/src/js/trap.rs Normal file
View File

@@ -0,0 +1,163 @@
// use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO};
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use wasm_bindgen::convert::FromWasmAbi;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
/// A struct representing an aborted instruction execution, with a message
/// indicating the cause.
#[wasm_bindgen]
#[derive(Clone)]
pub struct WasmerRuntimeError {
inner: Arc<RuntimeErrorSource>,
}
/// This type is the same as `WasmerRuntimeError`.
///
/// We use the `WasmerRuntimeError` name to not collide with the
/// `RuntimeError` in JS.
pub type RuntimeError = WasmerRuntimeError;
impl PartialEq for RuntimeError {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
/// The source of the `RuntimeError`.
#[derive(Debug)]
enum RuntimeErrorSource {
Generic(String),
User(Box<dyn Error + Send + Sync>),
Js(JsValue),
}
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::Js(s) => write!(f, "{}", s.as_string().unwrap_or("".to_string())),
}
}
}
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 {
RuntimeError {
inner: Arc::new(RuntimeErrorSource::Generic(message.into())),
}
}
/// Creates a new user `RuntimeError` with the given `error`.
pub fn user(error: impl Error + Send + Sync + 'static) -> Self {
RuntimeError {
inner: Arc::new(RuntimeErrorSource::User(Box::new(error))),
}
}
/// Raises a custom user Error
pub fn raise(error: Box<dyn Error + Send + Sync>) -> ! {
let error = if error.is::<RuntimeError>() {
*error.downcast::<RuntimeError>().unwrap()
} else {
RuntimeError {
inner: Arc::new(RuntimeErrorSource::User(error)),
}
};
let js_error: JsValue = error.into();
wasm_bindgen::throw_val(js_error)
}
/// Returns a reference the `message` stored in `Trap`.
pub fn message(&self) -> String {
format!("{}", self.inner)
}
/// 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(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 true if the `RuntimeError` is the same as T
pub fn is<T: Error + 'static>(&self) -> bool {
match self.inner.as_ref() {
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)
.finish()
}
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RuntimeError: {}", self.message())?;
Ok(())
}
}
impl std::error::Error for RuntimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self.inner.as_ref() {
RuntimeErrorSource::User(err) => Some(&**err),
_ => None,
}
}
}
pub fn generic_of_jsval<T: FromWasmAbi<Abi = u32>>(
js: JsValue,
classname: &str,
) -> Result<T, JsValue> {
use js_sys::{Object, Reflect};
let ctor_name = Object::get_prototype_of(&js).constructor().name();
if ctor_name == classname {
let ptr = Reflect::get(&js, &JsValue::from_str("ptr"))?;
match ptr.as_f64() {
Some(ptr_f64) => {
let foo = unsafe { T::from_abi(ptr_f64 as u32) };
Ok(foo)
}
None => {
// We simply relay the js value
Err(js)
}
}
} else {
Err(js)
}
}
impl From<JsValue> for RuntimeError {
fn from(original: JsValue) -> Self {
// We try to downcast the error and see if it's
// an instance of RuntimeError instead, so we don't need
// to re-wrap it.
generic_of_jsval(original, "WasmerRuntimeError").unwrap_or_else(|js| RuntimeError {
inner: Arc::new(RuntimeErrorSource::Js(js)),
})
}
}

52
lib/api/src/js/types.rs Normal file
View File

@@ -0,0 +1,52 @@
use crate::js::externals::Function;
// use crate::js::store::{Store, StoreObject};
// use crate::js::RuntimeError;
use wasm_bindgen::JsValue;
use wasmer_types::Value;
pub use wasmer_types::{
ExportType, ExternType, FunctionType, GlobalType, ImportType, MemoryType, Mutability,
TableType, Type as ValType,
};
/// WebAssembly computations manipulate values of basic value types:
/// * Integers (32 or 64 bit width)
/// * Floating-point (32 or 64 bit width)
/// * Vectors (128 bits, with 32 or 64 bit lanes)
///
/// Spec: <https://webassembly.github.io/spec/core/exec/runtime.html#values>
// pub type Val = ();
pub type Val = Value<Function>;
pub trait AsJs {
fn as_jsvalue(&self) -> JsValue;
}
#[inline]
pub fn param_from_js(ty: &ValType, js_val: &JsValue) -> Val {
match ty {
ValType::I32 => Val::I32(js_val.as_f64().unwrap() as _),
ValType::I64 => Val::I64(js_val.as_f64().unwrap() as _),
ValType::F32 => Val::F32(js_val.as_f64().unwrap() as _),
ValType::F64 => Val::F64(js_val.as_f64().unwrap()),
t => unimplemented!(
"The type `{:?}` is not yet supported in the JS Function API",
t
),
}
}
impl AsJs for Val {
fn as_jsvalue(&self) -> JsValue {
match self {
Self::I32(i) => JsValue::from_f64(*i as f64),
Self::I64(i) => JsValue::from_f64(*i as f64),
Self::F32(f) => JsValue::from_f64(*f as f64),
Self::F64(f) => JsValue::from_f64(*f),
Self::FuncRef(func) => func.as_ref().unwrap().exported.function.clone().into(),
v => unimplemented!(
"The value `{:?}` is not yet supported in the JS Function API",
v
),
}
}
}

4
lib/api/src/js/utils.rs Normal file
View File

@@ -0,0 +1,4 @@
/// Check if the provided bytes are wasm-like
pub fn is_wasm(bytes: impl AsRef<[u8]>) -> bool {
bytes.as_ref().starts_with(b"\0asm")
}

View File

@@ -0,0 +1,31 @@
use js_sys::Object;
use wasm_bindgen::prelude::*;
// WebAssembly.Global
#[wasm_bindgen]
extern "C" {
/// The `WebAssembly.Global()` constructor creates a new `Global` object
/// of the given type and value.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global)
#[wasm_bindgen(js_namespace = WebAssembly, extends = Object, typescript_type = "WebAssembly.Global")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type Global;
/// The `WebAssembly.Global()` constructor creates a new `Global` object
/// of the given type and value.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global)
#[wasm_bindgen(constructor, js_namespace = WebAssembly, catch)]
pub fn new(global_descriptor: &Object, value: &JsValue) -> Result<Global, JsValue>;
/// The value prototype property of the `WebAssembly.Global` object
/// returns the value of the global.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global)
#[wasm_bindgen(method, getter, structural, js_namespace = WebAssembly)]
pub fn value(this: &Global) -> JsValue;
#[wasm_bindgen(method, setter = value, structural, js_namespace = WebAssembly)]
pub fn set_value(this: &Global, value: &JsValue);
}