Added support for Globals

This commit is contained in:
Syrus Akbary
2021-07-13 14:16:48 -07:00
parent a4d4b3fc0d
commit 0735a5e93f
8 changed files with 226 additions and 121 deletions

View File

@@ -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::<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(

View File

@@ -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: <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 {
@@ -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<Self, RuntimeError> {
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> {

View File

@@ -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> {

View File

@@ -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<Function> for Extern {
}
}
// impl From<Global> for Extern {
// fn from(r: Global) -> Self {
// Self::Global(r)
// }
// }
impl From<Global> for Extern {
fn from(r: Global) -> Self {
Self::Global(r)
}
}
impl From<Memory> for Extern {
fn from(r: Memory) -> Self {

View File

@@ -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};

View File

@@ -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<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);
}

View File

@@ -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(())
// }
// #[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));
#[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_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));
}
// Ok(())
// }
#[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());
// #[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());
// 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() {

View File

@@ -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())
);
}