Major fixes for WebAssembly
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Deukhoofd 2022-07-18 15:36:03 +02:00
parent 0961b199ff
commit 703fd2c147
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
11 changed files with 129 additions and 81 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
Cargo.lock
.idea/
.idea/
types.toml

View File

@ -505,7 +505,7 @@ impl<'own, 'library> Pokemon<'own, 'library> {
.set(ability_script)
.as_ref()
// Ensure the ability script gets initialized with the parameters for the ability.
.on_initialize(self.active_ability().parameters())
.on_initialize(self.library, self.active_ability().parameters())
} else {
self.ability_script.clear();
}

View File

@ -225,6 +225,7 @@ mod tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use crate::dynamic_data::script_handling::script::ScriptContainer;
use crate::dynamic_data::DynamicLibrary;
use crate::static_data::EffectParameter;
use crate::StringKey;
@ -273,7 +274,7 @@ mod tests {
fn remove_suppression(&self) {}
fn on_initialize(&self, _pars: &[EffectParameter]) {
fn stack(&self) {
self.test_count.fetch_add(1, Ordering::SeqCst);
}
@ -292,7 +293,7 @@ mod tests {
let scripts = vec![ScriptWrapper::from(&script)];
let mut aggregator = ScriptIterator::new(&scripts as *const Vec<ScriptWrapper>);
while let Some(v) = aggregator.get_next() {
v.get().unwrap().read().as_ref().unwrap().on_initialize(&[]);
v.get().unwrap().read().as_ref().unwrap().stack();
}
let a = script.get_as::<TestScript>();
assert_eq!(a.test_count.load(Ordering::Relaxed), 1);
@ -306,7 +307,7 @@ mod tests {
for i in 1..11 {
aggregator.reset();
while let Some(v) = aggregator.get_next() {
v.get().unwrap().read().as_ref().unwrap().on_initialize(&[]);
v.get().unwrap().read().as_ref().unwrap().stack();
}
let a = script.get_as::<TestScript>();
assert_eq!(a.test_count.load(Ordering::Relaxed), i);
@ -325,7 +326,7 @@ mod tests {
];
let mut aggregator = ScriptIterator::new(&scripts as *const Vec<ScriptWrapper>);
while let Some(v) = aggregator.get_next() {
v.get().unwrap().read().as_ref().unwrap().on_initialize(&[]);
v.get().unwrap().read().as_ref().unwrap().stack();
}
let a = script1.get_as::<TestScript>();
assert_eq!(a.test_count.load(Ordering::Relaxed), 1);
@ -349,7 +350,7 @@ mod tests {
for i in 1..11 {
aggregator.reset();
while let Some(v) = aggregator.get_next() {
v.get().unwrap().read().as_ref().unwrap().on_initialize(&[]);
v.get().unwrap().read().as_ref().unwrap().stack();
}
let a = script1.get_as::<TestScript>();
assert_eq!(a.test_count.load(Ordering::Relaxed), i);
@ -372,7 +373,7 @@ mod tests {
for i in 1..11 {
aggregator.reset();
while let Some(v) = aggregator.get_next() {
v.get().unwrap().read().as_ref().unwrap().on_initialize(&[]);
v.get().unwrap().read().as_ref().unwrap().stack();
}
let s = set.at(0);
let s = s.get_as::<TestScript>();

View File

@ -8,10 +8,10 @@ use std::thread::JoinHandle;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use crate::dynamic_data::choices::{MoveChoice, TurnChoice};
use crate::dynamic_data::Battle;
use crate::dynamic_data::DamageSource;
use crate::dynamic_data::ExecutingMove;
use crate::dynamic_data::Pokemon;
use crate::dynamic_data::{Battle, DynamicLibrary};
use crate::static_data::{EffectParameter, TypeIdentifier};
use crate::static_data::{Item, Statistic};
use crate::StringKey;
@ -64,7 +64,7 @@ pub trait Script: Send + Sync {
/// This function is ran when this script stops being in effect, and is removed from its owner.
fn on_remove(&self) {}
/// This function is ran when this script starts being in effect.
fn on_initialize(&self, _pars: &[EffectParameter]) {}
fn on_initialize(&self, _library: &DynamicLibrary, _pars: &[EffectParameter]) {}
/// This function is ran just before the start of the turn. Everyone has made its choices here,
/// and the turn is about to start. This is a great place to initialize data if you need to know
/// something has happened during a turn.
@ -423,7 +423,7 @@ mod tests {
&self.suppressed_count
}
fn on_initialize(&self, _pars: &[EffectParameter]) {
fn stack(&self) {
unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().clear() }
}
@ -451,7 +451,7 @@ mod tests {
drop(w);
// Initialize with the script being taken as read lock. This prevents the script from actually
// removing itself, as it's still doing things.
container.script.read().as_ref().unwrap().on_initialize(&[]);
container.script.read().as_ref().unwrap().stack();
// If we now try and get the script, it will be none the first time. This has the side effect
// of actually disposing of the script.
assert!(container.get().is_none());

View File

@ -1,6 +1,5 @@
use core::ffi::c_char;
use std::ffi::CString;
use std::mem::align_of;
use std::mem::{align_of, forget};
use wasmer::{Exports, Function, Store};
@ -10,6 +9,7 @@ use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv;
use crate::static_data::{DataLibrary, MoveData, MoveLibrary, StaticData};
use crate::StringKey;
#[allow(unused_macros)]
macro_rules! register_func {
($exports: ident, $store: ident, $func: ident) => {
$exports.insert(stringify!($func), Function::new_native($store, $func));
@ -39,19 +39,22 @@ pub(crate) fn register_webassembly_funcs(exports: &mut Exports, store: &Store, e
fn _print(env: &WebAssemblyEnv, p: u32, len: u32) {
unsafe {
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(p as isize);
let mem: *mut u8 = env.data().memory().data_ptr().offset(p as isize);
let s = String::from_raw_parts(mem, len as usize, len as usize);
println!("{}", s);
forget(s);
}
}
fn _error(env: &WebAssemblyEnv, message: u32, message_len: u32, file: u32, file_len: u32, line: u32, position: u32) {
unsafe {
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(message as isize);
let mem: *mut u8 = env.data().memory().data_ptr().offset(message as isize);
let message = String::from_raw_parts(mem, message_len as usize, message_len as usize);
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(file as isize);
let mem: *mut u8 = env.data().memory().data_ptr().offset(file as isize);
let file = String::from_raw_parts(mem, file_len as usize, file_len as usize);
println!("Error: {} in file {}, line: {}:{}", message, file, line, position);
forget(message);
forget(file);
}
}
@ -59,7 +62,7 @@ fn move_library_get_move_by_hash(env: &WebAssemblyEnv, lib: ExternRef<MoveLibrar
let lib = lib.value(env).unwrap();
let m = lib.get_by_hash(hash);
if let Some(v) = m {
ExternRef::new(env, v)
ExternRef::new(env.data().as_ref(), v)
} else {
ExternRef::null()
}
@ -67,7 +70,7 @@ fn move_library_get_move_by_hash(env: &WebAssemblyEnv, lib: ExternRef<MoveLibrar
fn move_data_get_name(env: &WebAssemblyEnv, move_data: ExternRef<MoveData>) -> ExternRef<StringKey> {
let move_data = move_data.value(env).unwrap();
ExternRef::new(env, move_data.name())
ExternRef::new(env.data().as_ref(), move_data.name())
}
fn move_data_get_base_power(env: &WebAssemblyEnv, move_data: ExternRef<MoveData>) -> u8 {
@ -80,12 +83,15 @@ fn const_string_get_hash(env: &WebAssemblyEnv, string_key: ExternRef<StringKey>)
fn const_string_get_str(env: &WebAssemblyEnv, string_key: ExternRef<StringKey>) -> u32 {
let string_key = string_key.value(env).unwrap().str();
let s: CString = CString::new(string_key.as_bytes()).unwrap();
let wasm_string_ptr = env
.resolver()
.allocate_mem(string_key.len() as u32, align_of::<CString>() as u32);
let mut wasm_string = unsafe { CString::from_raw(wasm_string_ptr.0 as *mut c_char) };
s.clone_into(&mut wasm_string);
.data()
.allocate_mem((string_key.len() + 1) as u32, align_of::<CString>() as u32);
let mut wasm_string: Vec<u8> =
unsafe { Vec::from_raw_parts(wasm_string_ptr.0, string_key.len() + 1, string_key.len() + 1) };
wasm_string.resize(string_key.len() + 1, 0);
string_key.as_bytes().clone_into(&mut wasm_string);
wasm_string.insert(string_key.len(), 0 as u8);
forget(wasm_string);
wasm_string_ptr.1
}
@ -93,9 +99,9 @@ fn battle_library_get_data_library(
env: &WebAssemblyEnv,
dynamic_lib: ExternRef<DynamicLibrary>,
) -> ExternRef<StaticData> {
ExternRef::new(env, dynamic_lib.value(env).unwrap().static_data())
ExternRef::new(env.data().as_ref(), dynamic_lib.value(env).unwrap().static_data())
}
fn data_library_get_move_library(env: &WebAssemblyEnv, data_library: ExternRef<StaticData>) -> ExternRef<MoveLibrary> {
ExternRef::new(env, data_library.value(env).unwrap().moves())
ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().moves())
}

View File

@ -3,17 +3,19 @@ use std::marker::PhantomData;
use unique_type_id::UniqueTypeId;
use wasmer::FromToNativeWasmType;
use crate::script_implementations::wasm::script_resolver::{WebAssemblyEnv, WebAssemblyScriptResolver};
use crate::script_implementations::wasm::script_resolver::{
WebAssemblyEnv, WebAssemblyEnvironmentData, WebAssemblyScriptResolver,
};
pub(crate) struct ExternRef<T: UniqueTypeId<u64>> {
pub(crate) struct ExternRef<T: UniqueTypeId<u64> + ?Sized> {
index: u32,
_phantom: PhantomData<T>,
}
impl<T: UniqueTypeId<u64>> ExternRef<T> {
pub fn new(env: &WebAssemblyEnv, value: &T) -> Self {
pub fn new(env: &WebAssemblyEnvironmentData, value: &T) -> Self {
Self {
index: env.resolver().get_extern_ref_index(value),
index: env.get_extern_ref_index(value),
_phantom: Default::default(),
}
}
@ -37,7 +39,7 @@ impl<T: UniqueTypeId<u64>> ExternRef<T> {
/// Returns the real value for a given ExternRef. Note that the requested type must be the same as the type of the
/// value when it was passed before. If these types do not match, this will panic.
pub fn value<'a, 'b>(&'a self, env: &'b WebAssemblyEnv) -> Option<&'b T> {
let ptr = env.resolver().get_extern_ref_value(self.index) as *const T;
let ptr = env.data().get_extern_ref_value(self.index) as *const T;
unsafe { ptr.as_ref() }
}
}

View File

@ -1,11 +1,15 @@
use std::any::Any;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize};
use std::sync::Weak;
use hashbrown::HashSet;
use wasmer::NativeFunc;
use crate::dynamic_data::Script;
use crate::script_implementations::wasm::script_resolver::WebAssemblyScriptResolver;
use crate::dynamic_data::{DynamicLibrary, Script};
use crate::script_implementations::wasm::extern_ref::ExternRef;
use crate::script_implementations::wasm::script_resolver::WebAssemblyEnvironmentData;
use crate::script_implementations::wasm::WebAssemblyScriptCapabilities;
use crate::static_data::EffectParameter;
use crate::StringKey;
/// A WebAssemblyScript is there to implement the Script trait within WebAssemblyScript.
@ -25,8 +29,8 @@ pub struct WebAssemblyScript {
self_ptr: u32,
/// Capabilities define which functions we actually implement.
capabilities: AtomicPtr<HashSet<WebAssemblyScriptCapabilities>>,
/// A reference back to our resolver.
resolver: AtomicPtr<WebAssemblyScriptResolver>,
/// The global runtime environment data.
environment: Weak<WebAssemblyEnvironmentData>,
}
impl WebAssemblyScript {
@ -35,7 +39,7 @@ impl WebAssemblyScript {
owner_ptr: *mut u8,
self_ptr: u32,
capabilities: *mut HashSet<WebAssemblyScriptCapabilities>,
resolver: *mut WebAssemblyScriptResolver,
environment: Weak<WebAssemblyEnvironmentData>,
name: StringKey,
) -> Self {
Self {
@ -45,7 +49,7 @@ impl WebAssemblyScript {
owner_ptr: AtomicPtr::new(owner_ptr),
self_ptr,
capabilities: AtomicPtr::new(capabilities),
resolver: AtomicPtr::new(resolver),
environment,
}
}
}
@ -63,6 +67,16 @@ impl Script for WebAssemblyScript {
&self.suppressed_count
}
fn on_initialize(&self, library: &DynamicLibrary, _pars: &[EffectParameter]) {
let env = self.environment.upgrade().unwrap();
let exported = env.exported_functions();
if let Some(f) = exported.get(&"script_on_initialize".into()) {
let func: NativeFunc<(u32, ExternRef<DynamicLibrary>, u32), ()> = f.native().unwrap();
func.call(self.self_ptr, ExternRef::new(env.as_ref(), library), 0)
.unwrap();
}
}
fn as_any(&self) -> &dyn Any {
self
}

View File

@ -1,11 +1,10 @@
use std::fmt::{Debug, Formatter};
use std::ops::DerefMut;
use std::sync::Arc;
use std::sync::{Arc, Weak};
use hashbrown::{HashMap, HashSet};
use parking_lot::lock_api::{MappedRwLockReadGuard, RwLockReadGuard};
use parking_lot::{RawRwLock, RwLock};
use unique_type_id::{TypeId, UniqueTypeId};
use unique_type_id::UniqueTypeId;
use wasmer::{
Cranelift, Exports, Extern, Features, Function, ImportObject, Instance, Memory, Module, NativeFunc, Store,
Universal, Value, WasmerEnv,
@ -28,8 +27,6 @@ pub struct WebAssemblyScriptResolver {
/// Our currently loaded WASM instances. Empty until finalize() is called, after which the loaded modules get turned
/// into actual instances.
instances: Vec<Instance>,
/// This is a map of all the functions that WASM gives us.
exported_functions: HashMap<StringKey, Function>,
/// This is the WASM function to load a script.
load_script_fn: Option<NativeFunc<(u8, ExternRef<StringKey>), u32>>,
@ -64,7 +61,6 @@ impl WebAssemblyScriptResolver {
store,
modules: Default::default(),
instances: Default::default(),
exported_functions: Default::default(),
load_script_fn: None,
script_capabilities: Default::default(),
environment_data: Arc::new(Default::default()),
@ -84,22 +80,21 @@ impl WebAssemblyScriptResolver {
let mut imports = ImportObject::new();
let mut exports = Exports::new();
let env = WebAssemblyEnv {
resolver: self.environment_data.clone(),
};
let env = WebAssemblyEnv::new(Arc::downgrade(&self.environment_data));
register_webassembly_funcs(&mut exports, &self.store, env);
imports.register("env", exports);
for module in &self.modules {
let instance = Instance::new(module, &imports).unwrap();
let exports = &instance.exports;
let mut exported_functions = self.environment_data.exported_functions.write();
for export in exports.iter() {
match export.1 {
Extern::Function(f) => {
self.exported_functions.insert(export.0.as_str().into(), f.clone());
exported_functions.insert(export.0.as_str().into(), f.clone());
}
Extern::Memory(m) => {
self.environment_data.memory.write().insert(m.clone());
let _ = self.environment_data.memory.write().insert(m.clone());
}
_ => {}
}
@ -107,11 +102,12 @@ impl WebAssemblyScriptResolver {
if let Some(m) = &self.environment_data.memory.read().as_ref() {
m.grow(32).unwrap();
}
if let Some(f) = self.exported_functions.get(&"load_script".into()) {
if let Some(f) = exported_functions.get(&"load_script".into()) {
self.load_script_fn = Some(f.native().unwrap())
}
if let Some(f) = self.exported_functions.get(&"allocate_mem".into()) {
self.environment_data
if let Some(f) = exported_functions.get(&"allocate_mem".into()) {
let _ = self
.environment_data
.allocate_mem_fn
.write()
.insert(f.native().unwrap());
@ -151,7 +147,12 @@ impl ScriptResolver for WebAssemblyScriptResolver {
if !self.script_capabilities.read().contains_key(&key) {
let mut capabilities = HashSet::new();
unsafe {
if let Some(get_cap) = self.exported_functions.get(&"get_script_capabilities".into()) {
if let Some(get_cap) = self
.environment_data
.exported_functions
.read()
.get(&"get_script_capabilities".into())
{
let res = get_cap.call(&[Value::I32(script as i32)]).unwrap();
let ptr = (self.environment_data.memory.read().as_ref().unwrap().data_ptr()
as *const WebAssemblyScriptCapabilities)
@ -173,7 +174,7 @@ impl ScriptResolver for WebAssemblyScriptResolver {
script,
capabilities as *const HashSet<WebAssemblyScriptCapabilities>
as *mut HashSet<WebAssemblyScriptCapabilities>,
self as *const WebAssemblyScriptResolver as *mut WebAssemblyScriptResolver,
Arc::downgrade(&self.environment_data),
script_key.clone(),
))))
}
@ -200,30 +201,44 @@ pub struct WebAssemblyEnvironmentData {
extern_ref_pointers: RwLock<Vec<*const u8>>,
/// To make sure we send the same identifier to WASM when we send the same piece of data multiple times, we have a
/// backwards lookup on extern_ref_pointers. This allows us to get the index for a given piece of data.
extern_ref_pointers_lookup: RwLock<HashMap<*const u8, u32>>,
extern_ref_pointers_lookup: RwLock<HashMap<ExternRefLookupKey, u32>>,
/// As an added security measure on our extern refs, we keep track of the types of the extern ref data we've sent.
/// This prevents illegal arbitrary memory operations, where we expect type X, but the actual type is Y, which would
/// allow for modifying memory we might not want to. If we get a type mismatch, we will panic, preventing this.
extern_ref_type_lookup: RwLock<HashMap<*const u8, TypeId<u64>>>,
extern_ref_type_lookup: RwLock<HashSet<ExternRefLookupKey>>,
/// The memory inside of the WASM container.
memory: RwLock<Option<Memory>>,
/// This is a map of all the functions that WASM gives us.
exported_functions: RwLock<HashMap<StringKey, Function>>,
/// This is the WASM function to allocate memory inside the WASM container.
allocate_mem_fn: RwLock<Option<NativeFunc<(u32, u32), u32>>>,
}
#[derive(Clone, Eq, PartialEq, Hash)]
struct ExternRefLookupKey {
pub ptr: *const u8,
pub t: u64,
}
impl WebAssemblyEnvironmentData {
/// This returns the memory of the WASM container.
pub fn memory(&self) -> MappedRwLockReadGuard<'_, RawRwLock, Memory> {
RwLockReadGuard::map(self.memory.read(), |a| a.as_ref().unwrap())
}
/// This returns the functions exported from WASM.
pub fn exported_functions(&self) -> RwLockReadGuard<'_, RawRwLock, HashMap<StringKey, Function>> {
self.exported_functions.read()
}
/// Allocates memory inside the WASM container with a given size and alignment. This memory is
/// owned by WASM, and is how we can pass memory references that the host allocated to WASM.
/// The return is a tuple containing both the actual pointer to the memory (usable by the host),
/// and the WASM offset to the memory (usable by the client).
pub fn allocate_mem(&self, size: u32, align: u32) -> (*const u8, u32) {
pub fn allocate_mem(&self, size: u32, align: u32) -> (*mut u8, u32) {
let wasm_ptr = self.allocate_mem_fn.read().as_ref().unwrap().call(size, align).unwrap();
unsafe {
(
@ -246,7 +261,11 @@ impl WebAssemblyEnvironmentData {
/// of this code.
pub fn get_extern_ref_index<T: UniqueTypeId<u64>>(&self, value: &T) -> u32 {
let ptr = value as *const T as *const u8;
if let Some(v) = self.extern_ref_pointers_lookup.read().get(&ptr) {
if let Some(v) = self
.extern_ref_pointers_lookup
.read()
.get(&ExternRefLookupKey { ptr, t: T::id().0 })
{
return *v as u32;
}
let index = {
@ -254,8 +273,12 @@ impl WebAssemblyEnvironmentData {
extern_ref_guard.push(ptr);
extern_ref_guard.len() as u32
};
self.extern_ref_pointers_lookup.write().insert(ptr, index);
self.extern_ref_type_lookup.write().insert(ptr, T::id());
self.extern_ref_pointers_lookup
.write()
.insert(ExternRefLookupKey { ptr, t: T::id().0 }, index);
self.extern_ref_type_lookup
.write()
.insert(ExternRefLookupKey { ptr, t: T::id().0 });
index
}
@ -264,8 +287,15 @@ impl WebAssemblyEnvironmentData {
pub fn get_extern_ref_value<T: UniqueTypeId<u64>>(&self, index: u32) -> &T {
let read_guard = self.extern_ref_pointers.read();
let ptr = read_guard.get((index - 1) as usize).unwrap();
let expected_type_id = &self.extern_ref_type_lookup.read()[ptr];
if expected_type_id.0 != T::id().0 {
if self
.extern_ref_type_lookup
.read()
.get(&ExternRefLookupKey {
ptr: *ptr,
t: T::id().0,
})
.is_none()
{
panic!(
"Extern ref was accessed with wrong type. Requested type {}, but this was not the type the extern ref was stored with.",
std::any::type_name::<T>()
@ -280,13 +310,18 @@ impl WebAssemblyEnvironmentData {
#[derive(Clone)]
pub(crate) struct WebAssemblyEnv {
/// A pointer to the WebAssemblyScriptResolver belonging to the current script environment.
pub resolver: Arc<WebAssemblyEnvironmentData>,
data: Weak<WebAssemblyEnvironmentData>,
}
impl WebAssemblyEnv {
/// Get the WebAssemblyScriptResolver belonging to the current context.
pub fn resolver(&self) -> &Arc<WebAssemblyEnvironmentData> {
&self.resolver
/// Instantiates a new Environment with the requested data.
pub fn new(data: Weak<WebAssemblyEnvironmentData>) -> Self {
Self { data }
}
/// Get the actual data belonging to the current context.
pub fn data(&self) -> Arc<WebAssemblyEnvironmentData> {
self.data.upgrade().unwrap()
}
}
@ -294,4 +329,8 @@ unsafe impl Sync for WebAssemblyEnv {}
unsafe impl Send for WebAssemblyEnv {}
unsafe impl Sync for WebAssemblyEnvironmentData {}
unsafe impl Send for WebAssemblyEnvironmentData {}
impl WasmerEnv for WebAssemblyEnv {}

Binary file not shown.

View File

@ -56,5 +56,5 @@ fn validate_script() {
.load_script(0 as *const u8, ScriptCategory::Move, &"test".into())
.unwrap()
.unwrap();
script.on_initialize(&[]);
script.on_initialize(&lib, &[]);
}

View File

@ -1,15 +0,0 @@
MoveLibrary = 0
MoveData = 1
StringKey = 2
DynamicLibrary = 3
StaticData = 4
DynamicLibrary = 0
MoveLibrary = 1
StaticData = 2
MoveData = 3
StringKey = 4
DynamicLibrary = 0
MoveLibrary = 1
StaticData = 2
MoveData = 3
StringKey = 4