Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add more types to polyfill wasmtime in js #3

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions crates/wasm-bridge-js/src/caller.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
use std::ops::{Deref, DerefMut};

use crate::DataHandle;
use crate::Store;

#[derive(Debug)]
pub struct Caller<T> {
handle: DataHandle<T>,
pub(crate) store: Store<T>,
}

impl<T> Caller<T> {
pub(crate) fn new(handle: DataHandle<T>) -> Self {
Self { handle }
pub(crate) fn new(store: Store<T>) -> Self {
Self { store }
}

pub fn data(&self) -> impl Deref<Target = T> + '_ {
self.handle.borrow()
self.store.data()
}

pub fn data_mut(&mut self) -> impl DerefMut<Target = T> + '_ {
self.handle.borrow_mut()
self.store.data_mut()
}
}

impl<T> Clone for Caller<T> {
fn clone(&self) -> Self {
Caller::new(self.handle.clone())
Caller::new(self.store.clone())
}
}
16 changes: 15 additions & 1 deletion crates/wasm-bridge-js/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Store;
use crate::{Caller, Store};

pub trait AsContext {
type Data;
Expand Down Expand Up @@ -30,6 +30,14 @@ impl<'a, T: AsContext> AsContext for &'a mut T {
}
}

impl<T> AsContext for Caller<T> {
type Data = T;

fn as_context(&self) -> &Store<Self::Data> {
&self.store
}
}

pub trait AsContextMut: AsContext {
fn as_context_mut(&mut self) -> &mut Store<Self::Data>;
}
Expand All @@ -45,3 +53,9 @@ impl<'a, T: AsContextMut> AsContextMut for &'a mut T {
T::as_context_mut(*self)
}
}

impl<T> AsContextMut for Caller<T> {
fn as_context_mut(&mut self) -> &mut Store<Self::Data> {
&mut self.store
}
}
16 changes: 16 additions & 0 deletions crates/wasm-bridge-js/src/conversions/from_js_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,19 @@ from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6
from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10));
#[rustfmt::skip]
from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10), (11, T11));

impl FromJsValue for Val {
type WasmAbi = JsValue;

fn from_js_value(value: &JsValue) -> Result<Self> {
let number = match value.as_f64() {
Some(number) => Ok(number),
None => Err(map_js_error("Expected a number")(value)),
}?;
Ok(Val::F64(f64::to_bits(number)))
}

fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
Self::from_js_value(&abi)
}
}
20 changes: 13 additions & 7 deletions crates/wasm-bridge-js/src/conversions/into_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::*;
pub(crate) type MakeClosure<T> = Box<dyn Fn(DataHandle<T>) -> (JsValue, DropHandler)>;

pub trait IntoMakeClosure<T, Params, Results> {
fn into_make_closure(self) -> MakeClosure<T>;
fn into_make_closure(self, engine: Engine) -> MakeClosure<T>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably more "correct" to "propagate" the same engine into the Caller like this, but the Engine struct is completely unused and is only there for compatibility with wasmtime, so I think it would just needlessly bloat the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Engine is used to construct the store in caller, which leads to impl AsContext and AsContextMut for Caller

}

impl<T, R, F> IntoMakeClosure<T, (), R> for F
Expand All @@ -16,11 +16,14 @@ where
F: Fn(Caller<T>) -> R + 'static,
R: ToJsValue + 'static,
{
fn into_make_closure(self) -> MakeClosure<T> {
fn into_make_closure(self, engine: Engine) -> MakeClosure<T> {
let self_rc = Rc::new(self);

let make_closure = move |handle: DataHandle<T>| {
let caller = Caller::new(handle);
let caller = Caller::new(Store {
engine: engine.clone(),
data: handle,
});
let self_clone = self_rc.clone();

let closure = Closure::<dyn Fn() -> Result<R::ReturnAbi, JsValue>>::new(move || {
Expand All @@ -42,11 +45,14 @@ macro_rules! into_make_closure_single {
F: Fn(Caller<T>, $ty) -> R + 'static,
R: ToJsValue + 'static,
{
fn into_make_closure(self) -> MakeClosure<T> {
fn into_make_closure(self, engine: Engine) -> MakeClosure<T> {
let self_rc = Rc::new(self);

let make_closure = move |handle: DataHandle<T>| {
let caller = Caller::new(handle);
let caller = Caller::new(Store {
engine: engine.clone(),
data: handle,
});
let self_clone = self_rc.clone();

let closure = Closure::<dyn Fn($ty) -> Result<R::ReturnAbi, JsValue>>::new(
Expand Down Expand Up @@ -78,11 +84,11 @@ macro_rules! into_make_closure_many {
$($name: FromWasmAbi + 'static,)*
R: ToJsValue + 'static,
{
fn into_make_closure(self) -> MakeClosure<T> {
fn into_make_closure(self, engine: Engine) -> MakeClosure<T> {
let self_rc = Rc::new(self);

let make_closure = move |handle: DataHandle<T>| {
let caller = Caller::new(handle);
let caller = Caller::new(Store { engine: engine.clone(), data: handle });
let self_clone = self_rc.clone();

let closure =
Expand Down
73 changes: 73 additions & 0 deletions crates/wasm-bridge-js/src/conversions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,76 @@ mod from_js_value_tests;
pub use from_js_value::*;
pub use into_closure::*;
pub use to_js_value::*;

/// Possible runtime values that a WebAssembly module can either consume or
/// produce.
#[derive(Debug, Clone)]
pub enum Val {
// NB: the ordering here is intended to match the ordering in
// `ValType` to improve codegen when learning the type of a value.
/// A 32-bit integer
I32(i32),

/// A 64-bit integer
I64(i64),

/// A 32-bit float.
///
/// Note that the raw bits of the float are stored here, and you can use
/// `f32::from_bits` to create an `f32` value.
F32(u32),

/// A 64-bit float.
///
/// Note that the raw bits of the float are stored here, and you can use
/// `f64::from_bits` to create an `f64` value.
F64(u64),
// /// A 128-bit number
// V128(u128),
// /// A first-class reference to a WebAssembly function.
// ///
// /// `FuncRef(None)` is the null function reference, created by `ref.null
// /// func` in Wasm.
// FuncRef(Option<Func>),

// /// An `externref` value which can hold opaque data to the Wasm instance
// /// itself.
// ///
// /// `ExternRef(None)` is the null external reference, created by `ref.null
// /// extern` in Wasm.
// ExternRef(Option<ExternRef>),
}

impl Val {
pub fn i32(&self) -> Option<i32> {
match self {
Val::I32(i) => Some(*i),
Val::I64(i) => Some(*i as i32),
Val::F32(i) => Some(f32::from_bits(*i) as i32),
Val::F64(i) => Some(f64::from_bits(*i) as i32),
_ => None,
}
}

pub fn f32(&self) -> Option<f32> {
match self {
Val::I32(i) => Some(*i as f32),
Val::I64(i) => Some(*i as f32),
Val::F32(i) => Some(f32::from_bits(*i)),
Val::F64(i) => Some(f64::from_bits(*i as u64) as f32),
_ => None,
}
}
}

impl From<f32> for Val {
fn from(value: f32) -> Self {
Val::F32(f32::to_bits(value))
}
}

impl From<i32> for Val {
fn from(value: i32) -> Self {
Val::I32(value)
}
}
38 changes: 38 additions & 0 deletions crates/wasm-bridge-js/src/conversions/to_js_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use wasm_bindgen::{
JsValue,
};

use crate::Val;

pub trait ToJsValue: Sized {
type ReturnAbi: ReturnWasmAbi + IntoWasmAbi;

Expand Down Expand Up @@ -286,3 +288,39 @@ to_js_value_many!(10, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6,
to_js_value_many!(11, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10));
#[rustfmt::skip]
to_js_value_many!(12, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10), (11, T11));

impl ToJsValue for Val {
type ReturnAbi = f64;

fn to_js_value(&self) -> JsValue {
let v = match self.clone() {
Val::I32(i) => i as f64,
Val::I64(i) => i as f64,
Val::F32(i) => f32::from_bits(i as u32) as f64,
Val::F64(i) => f64::from_bits(i),
};
JsValue::from_f64(v)
}

fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
let v = match self.clone() {
Val::I32(i) => i as f64,
Val::I64(i) => i as f64,
Val::F32(i) => f32::from_bits(i as u32) as f64,
Val::F64(i) => f64::from_bits(i),
};
Ok(v)
}
}

impl ToJsValue for anyhow::Error {
type ReturnAbi = js_sys::Error;

fn to_js_value(&self) -> JsValue {
js_sys::Error::new(&format!("{:?}", self)).into()
}

fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
Ok(js_sys::Error::new(&format!("{:?}", self)))
}
}
50 changes: 50 additions & 0 deletions crates/wasm-bridge-js/src/func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use anyhow::bail;
use js_sys::{Function, WebAssembly};

use crate::{helpers::map_js_error, AsContextMut, Error, FromJsValue, ToJsValue, Val};

pub struct Func {
instance: WebAssembly::Instance,
function: Function,
}

impl Func {
pub fn new(instance: WebAssembly::Instance, function: Function) -> Self {
Func { instance, function }
}

pub fn call(
&self,
_: impl AsContextMut,
params: &[Val],
results: &mut [Val],
) -> Result<(), Error> {
let params = params
.iter()
.map(|val| val.to_js_value())
.collect::<js_sys::Array>();
let result = self
.function
.apply(&self.function, &params)
.map_err(map_js_error("Exported function threw an exception"))?;
let data = if result.is_array() {
Vec::<Val>::from_js_value(&result)?
} else if result.is_undefined() || result.is_null() {
vec![]
} else {
vec![Val::from_js_value(&result)?]
};
if data.len() != results.len() {
bail!(
"Exported function {} should have {} arguments, but it has {} instead.",
self.function.name().as_string().unwrap_or_default(),
results.len(),
data.len()
);
}
for (target, source) in results.iter_mut().zip(data.into_iter()) {
*target = source;
}
Ok(())
}
}
19 changes: 18 additions & 1 deletion crates/wasm-bridge-js/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ impl Instance {
);
}

Ok(TypedFunc::new(&self.instance, function))
Ok(TypedFunc::new(self.instance.clone(), function))
}

pub fn get_func(&self, _store: impl AsContextMut, name: &str) -> Option<Func> {
let function = Reflect::get(&self.exports, &name.into()).ok()?;

if !function.is_function() {
return None;
}

let function: Function = function.into();

Some(Func::new(self.instance.clone(), function))
}

pub fn get_memory<T>(&self, _: &mut Store<T>, id: &str) -> Option<Memory> {
let memory = Reflect::get(&self.exports, &id.into()).ok()?.into();
Some(Memory { memory })
}
}
6 changes: 6 additions & 0 deletions crates/wasm-bridge-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ pub use config::*;
mod context;
pub use context::*;

mod memory;
pub use memory::*;

mod func;
pub use func::*;

pub type Error = anyhow::Error;
pub type Result<T, E = Error> = anyhow::Result<T, E>;

Expand Down
10 changes: 7 additions & 3 deletions crates/wasm-bridge-js/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ use wasm_bindgen::JsValue;
use crate::*;

pub struct Linker<T> {
engine: Engine,
fns: Vec<PreparedFn<T>>,
}

impl<T> Linker<T> {
pub fn new(_engine: &Engine) -> Self {
Self { fns: vec![] }
pub fn new(engine: &Engine) -> Self {
Self {
engine: engine.clone(),
fns: vec![],
}
}

pub fn instantiate(
Expand Down Expand Up @@ -39,7 +43,7 @@ impl<T> Linker<T> {
where
F: IntoMakeClosure<T, Params, Results> + 'static,
{
let creator = func.into_make_closure();
let creator = func.into_make_closure(self.engine.clone());

self.fns.push(PreparedFn::new(module, name, creator));

Expand Down
Loading