diff --git a/lib/js-api/src/export.rs b/lib/js-api/src/export.rs index 5f7265c6c..7beb5b5a1 100644 --- a/lib/js-api/src/export.rs +++ b/lib/js-api/src/export.rs @@ -1,4 +1,5 @@ use crate::instance::Instance; +use crate::wasm_bindgen_polyfill::Global; use crate::WasmerEnv; use js_sys::Function; use js_sys::WebAssembly::{Memory, Table}; @@ -7,7 +8,7 @@ use std::fmt; use std::sync::Arc; use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; -use wasmer_types::{ExternType, FunctionType, MemoryType, TableType}; +use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType}; #[derive(Clone, Debug, PartialEq)] pub struct VMMemory { @@ -21,6 +22,18 @@ impl VMMemory { } } +#[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, @@ -85,8 +98,9 @@ pub enum Export { /// A memory export value. Memory(VMMemory), - // /// A global export value. - // Global(VMGlobal), + + /// A global export value. + Global(VMGlobal), } impl Export { @@ -95,6 +109,7 @@ impl Export { 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(), } } } @@ -112,6 +127,16 @@ impl From<(JsValue, ExternType)> for Export { panic!("Extern type doesn't match js value type"); } } + ExternType::Global(global_type) => { + if val.is_instance_of::() { + return Export::Global(VMGlobal::new( + val.unchecked_into::(), + global_type, + )); + } else { + panic!("Extern type doesn't match js value type"); + } + } ExternType::Function(function_type) => { if val.is_instance_of::() { return Export::Function(VMFunction::new( diff --git a/lib/js-api/src/externals/global.rs b/lib/js-api/src/externals/global.rs index 7eb424b8f..096e81b32 100644 --- a/lib/js-api/src/externals/global.rs +++ b/lib/js-api/src/externals/global.rs @@ -1,14 +1,16 @@ +use crate::export::Export; +use crate::export::VMGlobal; use crate::exports::{ExportError, Exportable}; use crate::externals::Extern; use crate::store::{Store, StoreObject}; -use crate::types::Val; +use crate::types::{Val, ValType}; +use crate::wasm_bindgen_polyfill::Global as JSGlobal; use crate::GlobalType; use crate::Mutability; use crate::RuntimeError; use std::fmt; use std::sync::Arc; -use wasmer_engine::Export; -use wasmer_vm::{Global as RuntimeGlobal, VMGlobal}; +use wasm_bindgen::JsValue; /// A WebAssembly `global` instance. /// @@ -16,7 +18,10 @@ use wasmer_vm::{Global as RuntimeGlobal, VMGlobal}; /// It consists of an individual value and a flag indicating whether it is mutable. /// /// Spec: +#[derive(Debug, Clone, PartialEq)] pub struct Global { + store: Store, + vm_global: VMGlobal, } impl Global { @@ -56,25 +61,33 @@ impl Global { /// Create a `Global` with the initial value [`Val`] and the provided [`Mutability`]. fn from_value(store: &Store, val: Val, mutability: Mutability) -> Result { - if !val.comes_from_same_store(store) { - return Err(RuntimeError::new("cross-`Store` globals are not supported")); - } - let global = RuntimeGlobal::new(GlobalType { + let global_ty = GlobalType { mutability, ty: val.ty(), - }); - unsafe { - global - .set_unchecked(val.clone()) - .map_err(|e| RuntimeError::new(format!("create global for {:?}: {}", val, e)))?; }; + 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: VMGlobal { - from: Arc::new(global), - instance_ref: None, - }, + vm_global: global, }) } @@ -93,7 +106,7 @@ impl Global { /// assert_eq!(v.ty(), &GlobalType::new(Type::I64, Mutability::Var)); /// ``` pub fn ty(&self) -> &GlobalType { - self.vm_global.from.ty() + &self.vm_global.ty } /// Returns the [`Store`] where the `Global` belongs. @@ -125,7 +138,13 @@ impl Global { /// assert_eq!(g.get(), Value::I32(1)); /// ``` pub fn get(&self) -> Val { - self.vm_global.from.get(&self.store) + 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. @@ -170,15 +189,20 @@ impl Global { /// g.set(Value::I64(2)).unwrap(); /// ``` pub fn set(&self, val: Val) -> Result<(), RuntimeError> { - if !val.comes_from_same_store(&self.store) { - return Err(RuntimeError::new("cross-`Store` values are not supported")); + if self.vm_global.ty.mutability == Mutability::Const { + return Err(RuntimeError::from_str("The global is immutable")); } - unsafe { - self.vm_global - .from - .set(val) - .map_err(|e| RuntimeError::new(format!("{}", e)))?; + if val.ty() != self.vm_global.ty.ty { + return Err(RuntimeError::from_str("The types don't match")); } + 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(()) } @@ -202,35 +226,13 @@ impl Global { /// assert!(g.same(&g)); /// ``` pub fn same(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.vm_global.from, &other.vm_global.from) - } -} - -impl Clone for Global { - fn clone(&self) -> Self { - let mut vm_global = self.vm_global.clone(); - vm_global.upgrade_instance_ref().unwrap(); - - Self { - store: self.store.clone(), - vm_global, - } - } -} - -impl fmt::Debug for Global { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter - .debug_struct("Global") - .field("ty", &self.ty()) - .field("value", &self.get()) - .finish() + self.vm_global == other.vm_global } } impl<'a> Exportable<'a> for Global { fn to_export(&self) -> Export { - self.vm_global.clone().into() + Export::Global(self.vm_global.clone()) } fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> { diff --git a/lib/js-api/src/externals/memory.rs b/lib/js-api/src/externals/memory.rs index 37322afc1..6c7ae5a37 100644 --- a/lib/js-api/src/externals/memory.rs +++ b/lib/js-api/src/externals/memory.rs @@ -309,8 +309,7 @@ impl Memory { impl<'a> Exportable<'a> for Memory { fn to_export(&self) -> Export { - unimplemented!(); - // self.vm_memory.clone().into() + Export::Memory(self.vm_memory.clone()) } fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> { diff --git a/lib/js-api/src/externals/mod.rs b/lib/js-api/src/externals/mod.rs index 07236ac76..eb336a6d1 100644 --- a/lib/js-api/src/externals/mod.rs +++ b/lib/js-api/src/externals/mod.rs @@ -1,5 +1,5 @@ pub(crate) mod function; -// mod global; +mod global; mod memory; mod table; @@ -7,7 +7,7 @@ pub use self::function::{ FromToNativeWasmType, Function, HostFunction, WasmTypeList, WithEnv, WithoutEnv, }; -// pub use self::global::Global; +pub use self::global::Global; pub use self::memory::Memory; pub use self::table::Table; @@ -25,8 +25,8 @@ use std::fmt; pub enum Extern { /// A external [`Function`]. Function(Function), - // /// A external [`Global`]. - // Global(Global), + /// A external [`Global`]. + Global(Global), /// A external [`Table`]. Table(Table), /// A external [`Memory`]. @@ -40,7 +40,7 @@ impl Extern { 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()), + Self::Global(gt) => ExternType::Global(*gt.ty()), } } @@ -49,7 +49,7 @@ impl Extern { 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::Global(g) => Self::Global(Global::from_vm_export(store, g)), Export::Table(t) => Self::Table(Table::from_vm_export(store, t)), } } @@ -59,7 +59,7 @@ 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::Global(g) => g.to_export(), Self::Memory(m) => m.to_export(), Self::Table(t) => t.to_export(), } @@ -80,7 +80,7 @@ impl fmt::Debug for Extern { "{}", match self { Self::Function(_) => "Function(...)", - // Self::Global(_) => "Global(...)", + Self::Global(_) => "Global(...)", Self::Memory(_) => "Memory(...)", Self::Table(_) => "Table(...)", } @@ -94,11 +94,11 @@ impl From for Extern { } } -// impl From for Extern { -// fn from(r: Global) -> Self { -// Self::Global(r) -// } -// } +impl From for Extern { + fn from(r: Global) -> Self { + Self::Global(r) + } +} impl From for Extern { fn from(r: Memory) -> Self { diff --git a/lib/js-api/src/lib.rs b/lib/js-api/src/lib.rs index 540894da2..72002d99a 100644 --- a/lib/js-api/src/lib.rs +++ b/lib/js-api/src/lib.rs @@ -294,6 +294,7 @@ mod module; #[cfg(feature = "wasm-types-polyfill")] mod module_info_polyfill; mod resolver; +mod wasm_bindgen_polyfill; // mod native; mod ptr; mod store; @@ -309,9 +310,7 @@ pub use crate::cell::WasmCell; pub use crate::env::{HostEnvInitError, LazyInit, WasmerEnv}; pub use crate::exports::{ExportError, Exportable, Exports, ExportsIterator}; pub use crate::externals::{ - Extern, FromToNativeWasmType, Function, HostFunction, Memory, Table, - /* Global, */ - WasmTypeList, + Extern, FromToNativeWasmType, Function, Global, HostFunction, Memory, Table, WasmTypeList, }; pub use crate::import_object::{ImportObject, ImportObjectIterator, LikeNamespace}; pub use crate::instance::{Instance, InstantiationError}; diff --git a/lib/js-api/src/wasm_bindgen_polyfill.rs b/lib/js-api/src/wasm_bindgen_polyfill.rs new file mode 100644 index 000000000..a378f3d48 --- /dev/null +++ b/lib/js-api/src/wasm_bindgen_polyfill.rs @@ -0,0 +1,32 @@ +use js_sys::Object; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// 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; + + /// 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); +} diff --git a/lib/js-api/tests/externals.rs b/lib/js-api/tests/externals.rs index 761e9de94..41bae93e9 100644 --- a/lib/js-api/tests/externals.rs +++ b/lib/js-api/tests/externals.rs @@ -2,62 +2,56 @@ use wasm_bindgen_test::*; // use anyhow::Result; use wasmer_js::*; -// #[test] -// fn global_new() -> Result<()> { -// let store = Store::default(); -// let global = Global::new(&store, Value::I32(10)); -// assert_eq!( -// *global.ty(), -// GlobalType { -// ty: Type::I32, -// mutability: Mutability::Const -// } -// ); +#[wasm_bindgen_test] +fn global_new() { + let store = Store::default(); + let global = Global::new(&store, Value::I32(10)); + assert_eq!( + *global.ty(), + GlobalType { + ty: Type::I32, + mutability: Mutability::Const + } + ); -// let global_mut = Global::new_mut(&store, Value::I32(10)); -// assert_eq!( -// *global_mut.ty(), -// GlobalType { -// ty: Type::I32, -// mutability: Mutability::Var -// } -// ); + let global_mut = Global::new_mut(&store, Value::I32(10)); + assert_eq!( + *global_mut.ty(), + GlobalType { + ty: Type::I32, + mutability: Mutability::Var + } + ); +} -// Ok(()) -// } +#[wasm_bindgen_test] +fn global_get() { + let store = Store::default(); + let global_i32 = Global::new(&store, Value::I32(10)); + assert_eq!(global_i32.get(), Value::I32(10)); + // let global_i64 = Global::new(&store, Value::I64(20)); + // assert_eq!(global_i64.get(), Value::I64(20)); + let global_f32 = Global::new(&store, Value::F32(10.0)); + assert_eq!(global_f32.get(), Value::F32(10.0)); + // let global_f64 = Global::new(&store, Value::F64(20.0)); + // assert_eq!(global_f64.get(), Value::F64(20.0)); +} -// #[test] -// fn global_get() -> Result<()> { -// let store = Store::default(); -// let global_i32 = Global::new(&store, Value::I32(10)); -// assert_eq!(global_i32.get(), Value::I32(10)); -// let global_i64 = Global::new(&store, Value::I64(20)); -// assert_eq!(global_i64.get(), Value::I64(20)); -// let global_f32 = Global::new(&store, Value::F32(10.0)); -// assert_eq!(global_f32.get(), Value::F32(10.0)); -// let global_f64 = Global::new(&store, Value::F64(20.0)); -// assert_eq!(global_f64.get(), Value::F64(20.0)); +#[wasm_bindgen_test] +fn global_set() { + let store = Store::default(); + let global_i32 = Global::new(&store, Value::I32(10)); + // Set on a constant should error + assert!(global_i32.set(Value::I32(20)).is_err()); -// Ok(()) -// } + let global_i32_mut = Global::new_mut(&store, Value::I32(10)); + // Set on different type should error + assert!(global_i32_mut.set(Value::I64(20)).is_err()); -// #[test] -// fn global_set() -> Result<()> { -// let store = Store::default(); -// let global_i32 = Global::new(&store, Value::I32(10)); -// // Set on a constant should error -// assert!(global_i32.set(Value::I32(20)).is_err()); - -// let global_i32_mut = Global::new_mut(&store, Value::I32(10)); -// // Set on different type should error -// assert!(global_i32_mut.set(Value::I64(20)).is_err()); - -// // Set on same type should succeed -// global_i32_mut.set(Value::I32(20))?; -// assert_eq!(global_i32_mut.get(), Value::I32(20)); - -// Ok(()) -// } + // Set on same type should succeed + global_i32_mut.set(Value::I32(20)).unwrap(); + assert_eq!(global_i32_mut.get(), Value::I32(20)); +} #[wasm_bindgen_test] fn table_new() { diff --git a/lib/js-api/tests/instance.rs b/lib/js-api/tests/instance.rs index f51096136..83649be37 100644 --- a/lib/js-api/tests/instance.rs +++ b/lib/js-api/tests/instance.rs @@ -290,3 +290,57 @@ fn test_imported_function_native_with_wasmer_env() { let expected = vec![Val::I32(36)].into_boxed_slice(); assert_eq!(exported.call(&[Val::I32(4)]), Ok(expected)); } + +#[wasm_bindgen_test] +fn test_imported_exported_global() { + let store = Store::default(); + let mut module = Module::new( + &store, + br#" + (module + (global $mut_i32_import (import "" "global") (mut i32)) + (func (export "getGlobal") (result i32) (global.get $mut_i32_import)) + (func (export "incGlobal") (global.set $mut_i32_import ( + i32.add (i32.const 1) (global.get $mut_i32_import) + ))) + ) + "#, + ) + .unwrap(); + module.set_type_hints(ModuleTypeHints { + imports: vec![ExternType::Global(GlobalType::new( + ValType::I32, + Mutability::Var, + ))], + exports: vec![ + ExternType::Function(FunctionType::new(vec![], vec![Type::I32])), + ExternType::Function(FunctionType::new(vec![], vec![])), + ], + }); + let mut global = Global::new_mut(&store, Value::I32(0)); + let import_object = imports! { + "" => { + "global" => global.clone() + } + }; + let instance = Instance::new(&module, &import_object).unwrap(); + + let get_global = instance.exports.get_function("getGlobal").unwrap(); + assert_eq!( + get_global.call(&[]), + Ok(vec![Val::I32(0)].into_boxed_slice()) + ); + + global.set(Value::I32(42)); + assert_eq!( + get_global.call(&[]), + Ok(vec![Val::I32(42)].into_boxed_slice()) + ); + + let inc_global = instance.exports.get_function("incGlobal").unwrap(); + inc_global.call(&[]); + assert_eq!( + get_global.call(&[]), + Ok(vec![Val::I32(43)].into_boxed_slice()) + ); +}