mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 05:38:19 +00:00
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:
140
lib/api/src/js/cell.rs
Normal file
140
lib/api/src/js/cell.rs
Normal 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
219
lib/api/src/js/env.rs
Normal 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
89
lib/api/src/js/error.rs
Normal 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
162
lib/api/src/js/export.rs
Normal 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
324
lib/api/src/js/exports.rs
Normal 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
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
242
lib/api/src/js/externals/global.rs
vendored
Normal 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
331
lib/api/src/js/externals/memory.rs
vendored
Normal 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
113
lib/api/src/js/externals/mod.rs
vendored
Normal 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
167
lib/api/src/js/externals/table.rs
vendored
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
412
lib/api/src/js/import_object.rs
Normal file
412
lib/api/src/js/import_object.rs
Normal 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
142
lib/api/src/js/instance.rs
Normal 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
144
lib/api/src/js/mod.rs
Normal 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
519
lib/api/src/js/module.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
600
lib/api/src/js/module_info_polyfill.rs
Normal file
600
lib/api/src/js/module_info_polyfill.rs
Normal 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
149
lib/api/src/js/native.rs
Normal 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
351
lib/api/src/js/ptr.rs
Normal 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
165
lib/api/src/js/resolver.rs
Normal 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
59
lib/api/src/js/store.rs
Normal 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
163
lib/api/src/js/trap.rs
Normal 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
52
lib/api/src/js/types.rs
Normal 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
4
lib/api/src/js/utils.rs
Normal 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")
|
||||
}
|
||||
31
lib/api/src/js/wasm_bindgen_polyfill.rs
Normal file
31
lib/api/src/js/wasm_bindgen_polyfill.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user