#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{boxed::Box, format, rc::Rc, string::String, sync::Arc};
use core::{
cell::{RefCell, RefMut},
error::Error,
fmt,
};
use smallvec::SmallVec;
use anyhow::{bail, Result};
use js_sys::{JsString, Object, Reflect, WebAssembly};
use slab::Slab;
use wasm_bindgen::{JsCast, JsValue};
use wasm_runtime_layer::{
backend::{AsContext, AsContextMut, Extern, Ref, Val, WasmEngine, WasmExternRef, WasmGlobal},
GlobalType, RefType, ValType,
};
const DEFAULT_ARGUMENT_SIZE: usize = 4;
type ArgumentVec<T> = SmallVec<[T; DEFAULT_ARGUMENT_SIZE]>;
mod conversion;
mod func;
mod instance;
mod memory;
mod module;
mod store;
mod table;
pub use func::Func;
pub use instance::Instance;
pub use memory::Memory;
pub use module::Module;
pub use store::{Store, StoreContext, StoreContextMut, StoreInner};
pub use table::Table;
use self::{
conversion::{FromJs, ToJs, ToStoredJs},
module::{ModuleInner, ParsedModule},
};
#[derive(Debug, Clone)]
pub(crate) struct JsErrorMsg {
message: String,
}
impl fmt::Display for JsErrorMsg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
impl Error for JsErrorMsg {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl From<&JsValue> for JsErrorMsg {
fn from(value: &JsValue) -> Self {
if let Some(v) = value.dyn_ref::<JsString>() {
Self { message: v.into() }
} else if let Ok(v) = Reflect::get(value, &"message".into()) {
Self {
message: v.as_string().expect("A string object"),
}
} else {
Self {
message: format!("{value:?}"),
}
}
}
}
impl From<JsValue> for JsErrorMsg {
fn from(value: JsValue) -> Self {
Self::from(&value)
}
}
impl WasmEngine for Engine {
type ExternRef = ExternRef;
type Func = Func;
type Global = Global;
type Instance = Instance;
type Memory = Memory;
type Module = Module;
type Store<T: 'static> = Store<T>;
type StoreContext<'a, T: 'static> = StoreContext<'a, T>;
type StoreContextMut<'a, T: 'static> = StoreContextMut<'a, T>;
type Table = Table;
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct DropResource(Box<dyn fmt::Debug>);
impl DropResource {
pub fn new(value: impl 'static + fmt::Debug) -> Self {
Self(Box::new(value))
}
}
#[derive(Default, Debug, Clone)]
pub struct Engine {
inner: Rc<RefCell<EngineInner>>,
}
impl Engine {
pub(crate) fn borrow_mut(&self) -> RefMut<'_, EngineInner> {
self.inner.borrow_mut()
}
}
#[derive(Default, Debug)]
pub(crate) struct EngineInner {
pub(crate) modules: Slab<ModuleInner>,
}
impl EngineInner {
pub fn insert_module(&mut self, module: ModuleInner, parsed: Arc<ParsedModule>) -> Module {
Module {
id: self.modules.insert(module),
parsed,
}
}
}
#[derive(Debug, Clone)]
pub struct Global {
pub(crate) id: usize,
}
#[derive(Debug)]
pub(crate) struct GlobalInner {
value: WebAssembly::Global,
ty: GlobalType,
}
impl ToStoredJs for Global {
type Repr = WebAssembly::Global;
fn to_stored_js<T>(&self, store: &StoreInner<T>) -> Result<WebAssembly::Global> {
let global = &store.globals[self.id];
Ok(global.value.clone())
}
}
impl Global {
pub(crate) fn from_exported_global<T>(
store: &mut StoreInner<T>,
value: JsValue,
signature: GlobalType,
) -> Option<Self> {
let global: &WebAssembly::Global = value.dyn_ref()?;
Some(store.insert_global(GlobalInner {
value: global.clone(),
ty: signature,
}))
}
}
impl WasmGlobal<Engine> for Global {
fn new(mut ctx: impl AsContextMut<Engine>, value: Val<Engine>, mutable: bool) -> Self {
let mut ctx = ctx.as_context_mut();
let ty = GlobalType::new(value.ty(), mutable);
let desc = Object::new();
Reflect::set(&desc, &"value".into(), &value.ty().to_js()).unwrap();
Reflect::set(&desc, &"mutable".into(), &mutable.into()).unwrap();
let value = value.to_stored_js(&ctx).unwrap();
let global = GlobalInner {
value: WebAssembly::Global::new(&desc, &value).unwrap(),
ty,
};
ctx.insert_global(global)
}
fn ty(&self, ctx: impl AsContext<Engine>) -> GlobalType {
ctx.as_context().globals[self.id].ty
}
fn set(&self, mut ctx: impl AsContextMut<Engine>, new_value: Val<Engine>) -> Result<()> {
let store: &mut StoreInner<_> = &mut ctx.as_context_mut();
let value = &new_value.to_stored_js(store)?;
let inner = &mut store.globals[self.id];
if !inner.ty.mutable() {
bail!("Global is not mutable");
}
inner.value.set_value(value);
Ok(())
}
fn get(&self, mut ctx: impl AsContextMut<Engine>) -> Val<Engine> {
let store: &mut StoreInner<_> = &mut ctx.as_context_mut();
let inner = &mut store.globals[self.id];
let ty = inner.ty;
let value = inner.value.value();
value_from_js_typed(store, &ty.content(), value).unwrap()
}
}
impl ToStoredJs for Val<Engine> {
type Repr = JsValue;
fn to_stored_js<T>(&self, store: &StoreInner<T>) -> Result<JsValue> {
match self {
Val::I32(v) => Ok((*v).into()),
Val::I64(v) => Ok((*v).into()),
Val::F32(v) => Ok((*v).into()),
Val::F64(v) => Ok((*v).into()),
Val::V128(_) => {
bail!("v128 values are not supported in the js_wasm_runtime_layer backend")
}
Val::FuncRef(None) => Ok(JsValue::NULL),
Val::FuncRef(Some(func)) => {
let v: &JsValue = store.funcs[func.id].func.as_ref();
Ok(v.clone())
}
Val::ExternRef(_) => bail!(
"extern references are not yet supported in the js_wasm_runtime_layer backend"
),
}
}
}
impl ToStoredJs for Ref<Engine> {
type Repr = JsValue;
fn to_stored_js<T>(&self, store: &StoreInner<T>) -> Result<JsValue> {
match self {
Ref::FuncRef(None) => Ok(JsValue::NULL),
Ref::FuncRef(Some(func)) => {
let v: &JsValue = store.funcs[func.id].func.as_ref();
Ok(v.clone())
}
Ref::ExternRef(_) => bail!(
"extern references are not yet supported in the js_wasm_runtime_layer backend"
),
}
}
}
#[derive(Debug, Clone)]
pub struct ExternRef {}
impl WasmExternRef<Engine> for ExternRef {
fn new<T: 'static + Send + Sync>(_: impl AsContextMut<Engine>, _: T) -> Self {
unimplemented!(
"extern references are not yet supported in the js_wasm_runtime_layer backend"
)
}
fn downcast<'a, 's: 'a, T: 'static, S: 'static>(
&self,
_: <Engine as WasmEngine>::StoreContext<'s, S>,
) -> Result<&'a T> {
bail!("extern references are not yet supported in the js_wasm_runtime_layer backend")
}
}
impl ToStoredJs for Extern<Engine> {
type Repr = JsValue;
fn to_stored_js<T>(&self, store: &StoreInner<T>) -> Result<JsValue> {
match self {
Extern::Global(v) => Ok(v.to_stored_js(store)?.into()),
Extern::Table(v) => Ok(v.to_stored_js(store)?.into()),
Extern::Memory(v) => Ok(v.to_stored_js(store)?.into()),
Extern::Func(v) => Ok(v.to_stored_js(store)?.into()),
}
}
}
impl ToJs for ValType {
type Repr = JsString;
fn to_js(&self) -> JsString {
match self {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
ValType::V128 => "v128",
ValType::FuncRef => "anyfunc",
ValType::ExternRef => "externref",
}
.into()
}
}
impl ToJs for RefType {
type Repr = JsString;
fn to_js(&self) -> JsString {
match self {
RefType::FuncRef => "anyfunc",
RefType::ExternRef => "externref",
}
.into()
}
}
impl FromJs for ValType {
fn from_js(value: JsValue) -> Option<Self>
where
Self: Sized,
{
let s = value.as_string()?;
let res = match &s[..] {
"i32" => Self::I32,
"i64" => Self::I64,
"f32" => Self::F32,
"f64" => Self::F64,
"v128" => Self::V128,
"anyfunc" => Self::FuncRef,
"externref" => Self::ExternRef,
_ => {
#[cfg(feature = "tracing")]
tracing::error!("Invalid value type {s:?}");
return None;
}
};
Some(res)
}
}
pub(crate) fn value_from_js_typed<T>(
_: &mut StoreInner<T>,
ty: &ValType,
value: JsValue,
) -> Option<Val<Engine>> {
match ty {
ValType::I32 => Some(Val::I32(i32::from_js(value)?)),
ValType::I64 => Some(Val::I64(i64::from_js(value)?)),
ValType::F32 => Some(Val::F32(f32::from_js(value)?)),
ValType::F64 => Some(Val::F64(f64::from_js(value)?)),
ValType::V128 => {
#[cfg(feature = "tracing")]
tracing::error!("v128 values are not supported in the js_wasm_runtime_layer backend");
None
}
ValType::FuncRef | ValType::ExternRef => {
#[cfg(feature = "tracing")]
tracing::error!(
"conversion to a function or extern outside of a module is not permitted"
);
None
}
}
}