#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec, vec::Vec};
#[cfg(feature = "wasmi")]
mod wasmi_impl;
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
#[macro_export]
macro_rules! if_wasmtime_is_enabled {
($($token:tt)*) => {};
}
#[cfg(all(feature = "std", feature = "wasmtime"))]
#[macro_export]
macro_rules! if_wasmtime_is_enabled {
($($token:tt)*) => {
$($token)*
}
}
if_wasmtime_is_enabled! {
pub use wasmtime;
}
#[cfg(feature = "std")]
pub type Result<T> = result::Result<T, String>;
#[cfg(not(feature = "std"))]
pub type Result<T> = result::Result<T, &'static str>;
#[derive(Copy, Clone, PartialEq, Debug, Eq)]
pub enum ValueType {
I32,
I64,
F32,
F64,
}
impl From<ValueType> for u8 {
fn from(val: ValueType) -> u8 {
match val {
ValueType::I32 => 0,
ValueType::I64 => 1,
ValueType::F32 => 2,
ValueType::F64 => 3,
}
}
}
impl TryFrom<u8> for ValueType {
type Error = ();
fn try_from(val: u8) -> sp_std::result::Result<ValueType, ()> {
match val {
0 => Ok(Self::I32),
1 => Ok(Self::I64),
2 => Ok(Self::F32),
3 => Ok(Self::F64),
_ => Err(()),
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)]
pub enum Value {
I32(i32),
I64(i64),
F32(u32),
F64(u64),
}
impl Value {
pub fn value_type(&self) -> ValueType {
match self {
Value::I32(_) => ValueType::I32,
Value::I64(_) => ValueType::I64,
Value::F32(_) => ValueType::F32,
Value::F64(_) => ValueType::F64,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self {
Self::I32(val) => Some(*val),
_ => None,
}
}
}
mod private {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
impl Sealed for u32 {}
impl Sealed for u64 {}
impl Sealed for i32 {}
impl Sealed for i64 {}
}
pub trait PointerType: Sized + private::Sealed {
const SIZE: u32 = mem::size_of::<Self>() as u32;
}
impl PointerType for u8 {}
impl PointerType for u16 {}
impl PointerType for u32 {}
impl PointerType for u64 {}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Pointer<T: PointerType> {
ptr: u32,
_marker: PhantomData<T>,
}
impl<T: PointerType> Pointer<T> {
pub fn new(ptr: u32) -> Self {
Self { ptr, _marker: Default::default() }
}
pub fn offset(self, offset: u32) -> Option<Self> {
offset
.checked_mul(T::SIZE)
.and_then(|o| self.ptr.checked_add(o))
.map(|ptr| Self { ptr, _marker: Default::default() })
}
pub fn null() -> Self {
Self::new(0)
}
pub fn cast<R: PointerType>(self) -> Pointer<R> {
Pointer::new(self.ptr)
}
}
impl<T: PointerType> From<u32> for Pointer<T> {
fn from(ptr: u32) -> Self {
Pointer::new(ptr)
}
}
impl<T: PointerType> From<Pointer<T>> for u32 {
fn from(ptr: Pointer<T>) -> Self {
ptr.ptr
}
}
impl<T: PointerType> From<Pointer<T>> for u64 {
fn from(ptr: Pointer<T>) -> Self {
u64::from(ptr.ptr)
}
}
impl<T: PointerType> From<Pointer<T>> for usize {
fn from(ptr: Pointer<T>) -> Self {
ptr.ptr as _
}
}
impl<T: PointerType> IntoValue for Pointer<T> {
const VALUE_TYPE: ValueType = ValueType::I32;
fn into_value(self) -> Value {
Value::I32(self.ptr as _)
}
}
impl<T: PointerType> TryFromValue for Pointer<T> {
fn try_from_value(val: Value) -> Option<Self> {
match val {
Value::I32(val) => Some(Self::new(val as _)),
_ => None,
}
}
}
pub type WordSize = u32;
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct Signature {
pub args: Cow<'static, [ValueType]>,
pub return_value: Option<ValueType>,
}
impl Signature {
pub fn new<T: Into<Cow<'static, [ValueType]>>>(
args: T,
return_value: Option<ValueType>,
) -> Self {
Self { args: args.into(), return_value }
}
pub fn new_with_args<T: Into<Cow<'static, [ValueType]>>>(args: T) -> Self {
Self { args: args.into(), return_value: None }
}
}
#[cfg(feature = "std")]
pub trait MaybeRefUnwindSafe: std::panic::RefUnwindSafe {}
#[cfg(feature = "std")]
impl<T: std::panic::RefUnwindSafe> MaybeRefUnwindSafe for T {}
#[cfg(not(feature = "std"))]
pub trait MaybeRefUnwindSafe {}
#[cfg(not(feature = "std"))]
impl<T> MaybeRefUnwindSafe for T {}
pub trait Function: MaybeRefUnwindSafe + Send + Sync {
fn name(&self) -> &str;
fn signature(&self) -> Signature;
fn execute(
&self,
context: &mut dyn FunctionContext,
args: &mut dyn Iterator<Item = Value>,
) -> Result<Option<Value>>;
}
impl PartialEq for dyn Function {
fn eq(&self, other: &Self) -> bool {
other.name() == self.name() && other.signature() == self.signature()
}
}
pub trait FunctionContext {
fn read_memory(&self, address: Pointer<u8>, size: WordSize) -> Result<Vec<u8>> {
let mut vec = vec![0; size as usize];
self.read_memory_into(address, &mut vec)?;
Ok(vec)
}
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> Result<()>;
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> Result<()>;
fn allocate_memory(&mut self, size: WordSize) -> Result<Pointer<u8>>;
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> Result<()>;
fn register_panic_error_message(&mut self, message: &str);
}
if_wasmtime_is_enabled! {
pub trait HostFunctionRegistry {
type State;
type Error;
type FunctionContext: FunctionContext;
fn with_function_context<R>(
caller: wasmtime::Caller<Self::State>,
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
) -> R;
fn register_static<Params, Results>(
&mut self,
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
) -> core::result::Result<(), Self::Error>;
}
}
pub trait HostFunctions: 'static + Send + Sync {
fn host_functions() -> Vec<&'static dyn Function>;
if_wasmtime_is_enabled! {
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry;
}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl HostFunctions for Tuple {
fn host_functions() -> Vec<&'static dyn Function> {
let mut host_functions = Vec::new();
for_tuples!( #( host_functions.extend(Tuple::host_functions()); )* );
host_functions
}
#[cfg(all(feature = "std", feature = "wasmtime"))]
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry,
{
for_tuples!(
#( Tuple::register_static(registry)?; )*
);
Ok(())
}
}
pub struct ExtendedHostFunctions<Base, Overlay> {
phantom: PhantomData<(Base, Overlay)>,
}
impl<Base, Overlay> HostFunctions for ExtendedHostFunctions<Base, Overlay>
where
Base: HostFunctions,
Overlay: HostFunctions,
{
fn host_functions() -> Vec<&'static dyn Function> {
let mut base = Base::host_functions();
let overlay = Overlay::host_functions();
base.retain(|host_fn| {
!overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name())
});
base.extend(overlay);
base
}
if_wasmtime_is_enabled! {
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry,
{
struct Proxy<'a, T> {
registry: &'a mut T,
seen_overlay: std::collections::HashSet<String>,
seen_base: std::collections::HashSet<String>,
overlay_registered: bool,
}
impl<'a, T> HostFunctionRegistry for Proxy<'a, T>
where
T: HostFunctionRegistry,
{
type State = T::State;
type Error = T::Error;
type FunctionContext = T::FunctionContext;
fn with_function_context<R>(
caller: wasmtime::Caller<Self::State>,
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
) -> R {
T::with_function_context(caller, callback)
}
fn register_static<Params, Results>(
&mut self,
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
) -> core::result::Result<(), Self::Error> {
if self.overlay_registered {
if !self.seen_base.insert(fn_name.to_owned()) {
log::warn!(
target: "extended_host_functions",
"Duplicate base host function: '{}'",
fn_name,
);
return Ok(())
}
if self.seen_overlay.contains(fn_name) {
log::debug!(
target: "extended_host_functions",
"Overriding base host function: '{}'",
fn_name,
);
return Ok(())
}
} else if !self.seen_overlay.insert(fn_name.to_owned()) {
log::warn!(
target: "extended_host_functions",
"Duplicate overlay host function: '{}'",
fn_name,
);
return Ok(())
}
self.registry.register_static(fn_name, func)
}
}
let mut proxy = Proxy {
registry,
seen_overlay: Default::default(),
seen_base: Default::default(),
overlay_registered: false,
};
Overlay::register_static(&mut proxy)?;
proxy.overlay_registered = true;
Base::register_static(&mut proxy)?;
Ok(())
}
}
}
#[cfg(all(feature = "std", feature = "wasmtime"))]
pub trait WasmTy: wasmtime::WasmTy + private::Sealed {}
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
pub trait WasmTy: private::Sealed {}
impl WasmTy for i32 {}
impl WasmTy for u32 {}
impl WasmTy for i64 {}
impl WasmTy for u64 {}
pub trait IntoValue {
const VALUE_TYPE: ValueType;
fn into_value(self) -> Value;
}
pub trait TryFromValue: Sized {
fn try_from_value(val: Value) -> Option<Self>;
}
macro_rules! impl_into_and_from_value {
(
$(
$type:ty, $( < $gen:ident >, )? $value_variant:ident,
)*
) => {
$(
impl $( <$gen> )? IntoValue for $type {
const VALUE_TYPE: ValueType = ValueType::$value_variant;
fn into_value(self) -> Value { Value::$value_variant(self as _) }
}
impl $( <$gen> )? TryFromValue for $type {
fn try_from_value(val: Value) -> Option<Self> {
match val {
Value::$value_variant(val) => Some(val as _),
_ => None,
}
}
}
)*
}
}
impl_into_and_from_value! {
u8, I32,
u16, I32,
u32, I32,
u64, I64,
i8, I32,
i16, I32,
i32, I32,
i64, I64,
}
#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)]
pub enum ReturnValue {
Unit,
Value(Value),
}
impl From<Value> for ReturnValue {
fn from(v: Value) -> ReturnValue {
ReturnValue::Value(v)
}
}
impl ReturnValue {
pub const ENCODED_MAX_SIZE: usize = 10;
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Encode;
#[test]
fn pointer_offset_works() {
let ptr = Pointer::<u32>::null();
assert_eq!(ptr.offset(10).unwrap(), Pointer::new(40));
assert_eq!(ptr.offset(32).unwrap(), Pointer::new(128));
let ptr = Pointer::<u64>::null();
assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80));
assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256));
}
#[test]
fn return_value_encoded_max_size() {
let encoded = ReturnValue::Value(Value::I64(-1)).encode();
assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE);
}
}