259 lines
8.2 KiB
Rust
Executable File
259 lines
8.2 KiB
Rust
Executable File
use anyhow_ext::anyhow;
|
|
use anyhow_ext::Result;
|
|
use num_traits::{NumCast, PrimInt};
|
|
use std::ffi::{c_char, CStr, CString};
|
|
use std::mem::{align_of, forget};
|
|
use std::ops::{ControlFlow, FromResidual, Try};
|
|
|
|
use wasmer::{FromToNativeWasmType, FunctionEnv, FunctionEnvMut, Imports, NativeWasmTypeInto, StoreMut};
|
|
|
|
use crate::script_implementations::wasm::extern_ref::ExternRef;
|
|
use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv;
|
|
use crate::static_data::EffectParameter;
|
|
use crate::StringKey;
|
|
|
|
/// Dynamic data registration
|
|
mod dynamic_data;
|
|
/// Static data registration
|
|
mod static_data;
|
|
|
|
/// Register a single function.
|
|
macro_rules! register_func_with_env {
|
|
($imports: ident, $store: ident, $func: ident, $env: expr) => {
|
|
$imports.define(
|
|
"env",
|
|
stringify!($func),
|
|
wasmer::Function::new_typed_with_env($store, $env, $func),
|
|
);
|
|
};
|
|
}
|
|
|
|
/// Utility macro to register a bunch of WASM functions easily.
|
|
macro_rules! register {
|
|
(
|
|
$(
|
|
fn $name:ident($($par:ident: $par_type:ty),*$(,)?) $(-> $return:ty)? $block:block
|
|
)*
|
|
$(
|
|
manual $manual_name:ident
|
|
)?
|
|
) => {
|
|
|
|
$(
|
|
fn $name(
|
|
$(
|
|
$par: $par_type,
|
|
)*
|
|
) $(-> $return)* $block
|
|
)*
|
|
|
|
pub(crate) fn register(imports: &mut wasmer::Imports,
|
|
store: &mut wasmer::StoreMut,
|
|
env: &wasmer::FunctionEnv<crate::script_implementations::wasm::script_resolver::WebAssemblyEnv>) {
|
|
$(
|
|
crate::script_implementations::wasm::export_registry::register_func_with_env!(imports, store, $name, env);
|
|
)*
|
|
$( $manual_name(imports, store, env) )*
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use register;
|
|
pub(crate) use register_func_with_env;
|
|
|
|
/// Register the functions we expose to WASM.
|
|
pub(crate) fn register_webassembly_funcs(
|
|
imports: &mut Imports,
|
|
store: &mut StoreMut,
|
|
env: &FunctionEnv<WebAssemblyEnv>,
|
|
) {
|
|
register_func_with_env!(imports, store, _print, env);
|
|
register_func_with_env!(imports, store, _error, env);
|
|
register_func_with_env!(imports, store, _vec_extern_ref_get_value, env);
|
|
|
|
static_data::register(imports, store, env);
|
|
dynamic_data::register(imports, store, env);
|
|
|
|
register_func_with_env!(imports, store, string_key_get_hash, env);
|
|
register_func_with_env!(imports, store, string_key_get_str, env);
|
|
register_func_with_env!(imports, store, effect_parameter_get_type, env);
|
|
register_func_with_env!(imports, store, effect_parameter_as_bool, env);
|
|
register_func_with_env!(imports, store, effect_parameter_as_int, env);
|
|
register_func_with_env!(imports, store, effect_parameter_as_float, env);
|
|
register_func_with_env!(imports, store, effect_parameter_as_string, env);
|
|
}
|
|
|
|
/// Logging function for WASM.
|
|
fn _print(env: FunctionEnvMut<WebAssemblyEnv>, p: u32) {
|
|
unsafe {
|
|
let mem: *mut c_char = env.data().data().get_raw_pointer(p);
|
|
let s = CStr::from_ptr(mem);
|
|
println!("{}", s.to_str().unwrap());
|
|
}
|
|
}
|
|
|
|
/// Triggers when WASM panics.
|
|
#[track_caller]
|
|
fn _error(env: FunctionEnvMut<WebAssemblyEnv>, message: u32, file: u32, line: u32, position: u32) {
|
|
unsafe {
|
|
let mem: *const c_char = env.data().data().get_raw_pointer(message);
|
|
let message_str = CStr::from_ptr(mem);
|
|
let mem: *const c_char = env.data().data().get_raw_pointer(file);
|
|
let file_str = CStr::from_ptr(mem);
|
|
|
|
panic!(
|
|
"WASM Error: {} in file {}, line: {}:{}",
|
|
message_str.to_str().unwrap(),
|
|
file_str.to_str().unwrap(),
|
|
line,
|
|
position
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Get a single item from an earlier passed VecExternRef
|
|
fn _vec_extern_ref_get_value(env: FunctionEnvMut<WebAssemblyEnv>, reference: u32, index: u32) -> WasmResult<u32> {
|
|
let res = env
|
|
.data()
|
|
.data()
|
|
.get_extern_vec_ref_extern_ref(reference as usize, index as usize)?;
|
|
Ok(res as u32).into()
|
|
}
|
|
|
|
/// Gets the hash value of a StringKey.
|
|
fn string_key_get_hash(env: FunctionEnvMut<WebAssemblyEnv>, string_key: ExternRef<StringKey>) -> WasmResult<u32> {
|
|
Ok(string_key.value_func(&env)?.hash()).into()
|
|
}
|
|
|
|
/// Get a null-terminated C string from a StringKey. Note that this involves a copy into WASM
|
|
/// memory, so this is relatively heavy.
|
|
fn string_key_get_str(env: FunctionEnvMut<WebAssemblyEnv>, string_key: ExternRef<StringKey>) -> WasmResult<u32> {
|
|
let string_key = string_key.value_func(&env)?.str();
|
|
let wasm_string_ptr = env
|
|
.data()
|
|
.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_u8);
|
|
forget(wasm_string);
|
|
Ok(wasm_string_ptr.1).into()
|
|
}
|
|
|
|
/// Gets the type of an EffectParameter
|
|
fn effect_parameter_get_type(
|
|
env: FunctionEnvMut<WebAssemblyEnv>,
|
|
parameter: ExternRef<EffectParameter>,
|
|
) -> WasmResult<u8> {
|
|
let v = parameter.value_func(&env)?;
|
|
Ok(match v {
|
|
EffectParameter::Bool(_, _) => 1,
|
|
EffectParameter::Int(_, _) => 2,
|
|
EffectParameter::Float(_, _) => 3,
|
|
EffectParameter::String(_, _) => 4,
|
|
})
|
|
.into()
|
|
}
|
|
|
|
/// Gets the inner bool data of an EffectParameter. Panics if it's not a bool.
|
|
fn effect_parameter_as_bool(
|
|
env: FunctionEnvMut<WebAssemblyEnv>,
|
|
parameter: ExternRef<EffectParameter>,
|
|
) -> WasmResult<u8> {
|
|
let v = parameter.value_func(&env)?;
|
|
match v {
|
|
EffectParameter::Bool(_, b) => Ok(<u8 as From<bool>>::from(*b)),
|
|
_ => Err(anyhow!("Unexpected parameter type. Expected bool, got {}", v)),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
/// Gets the inner int data of an EffectParameter. Panics if it's not an int.
|
|
fn effect_parameter_as_int(
|
|
env: FunctionEnvMut<WebAssemblyEnv>,
|
|
parameter: ExternRef<EffectParameter>,
|
|
) -> WasmResult<i64> {
|
|
let v = parameter.value_func(&env)?;
|
|
match v {
|
|
EffectParameter::Int(_, i) => Ok(*i),
|
|
_ => Err(anyhow!("Unexpected parameter type. Expected int, got {}", v)),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
/// Gets the inner float data of an EffectParameter. Panics if it's not a float.
|
|
fn effect_parameter_as_float(
|
|
env: FunctionEnvMut<WebAssemblyEnv>,
|
|
parameter: ExternRef<EffectParameter>,
|
|
) -> WasmResult<f32> {
|
|
let v = parameter.value_func(&env)?;
|
|
match v {
|
|
EffectParameter::Float(_, f) => Ok(*f),
|
|
_ => Err(anyhow!("Unexpected parameter type. Expected float, got {}", v)),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
/// Gets the inner string data of an EffectParameter. Panics if it's not a string.
|
|
fn effect_parameter_as_string(
|
|
env: FunctionEnvMut<WebAssemblyEnv>,
|
|
parameter: ExternRef<EffectParameter>,
|
|
) -> WasmResult<ExternRef<StringKey>> {
|
|
let v = parameter.value_func(&env)?;
|
|
match v {
|
|
EffectParameter::String(_, s) => Ok(ExternRef::func_new(&env, s)),
|
|
_ => Err(anyhow!("Unexpected parameter type. Expected string, got {}", v)),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
/// A result type that can be given to, or returned from WASM.
|
|
struct WasmResult<T>(Result<T>);
|
|
|
|
impl<T> WasmResult<T> {
|
|
/// Create a new WasmResult with an OK value.
|
|
pub fn ok(value: T) -> Self {
|
|
Self(Ok(value))
|
|
}
|
|
|
|
/// Create a new WasmResult with an Err value.
|
|
pub fn err(value: anyhow_ext::Error) -> Self {
|
|
Self(Err(value))
|
|
}
|
|
}
|
|
|
|
impl<T> From<Result<T>> for WasmResult<T> {
|
|
fn from(value: Result<T>) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl<T> FromResidual for WasmResult<T> {
|
|
fn from_residual(residual: <Self as Try>::Residual) -> Self {
|
|
match residual {
|
|
Ok(_) => {
|
|
unimplemented!()
|
|
}
|
|
Err(e) => WasmResult(Err(e)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Try for WasmResult<T> {
|
|
type Output = T;
|
|
type Residual = <Result<T> as Try>::Residual;
|
|
|
|
fn from_output(output: Self::Output) -> Self {
|
|
Self(Ok(output))
|
|
}
|
|
|
|
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
|
|
match self.0 {
|
|
Ok(v) => ControlFlow::Continue(v),
|
|
Err(e) => ControlFlow::Break(Err(e)),
|
|
}
|
|
}
|
|
}
|