use std::mem;
use fvm_shared::error::ErrorNumber;
use fvm_shared::sys::SyscallSafe;
use wasmtime::{Caller, WasmTy};
use super::context::Memory;
use super::error::Abort;
use super::{Context, InvocationData, charge_for_exec, charge_syscall_gas, update_gas_available};
use crate::call_manager::backtrace;
use crate::kernel::{self, ExecutionError, Kernel, SyscallError};
pub struct Linker<K>(pub(crate) wasmtime::Linker<InvocationData<K>>);
impl<K> Linker<K> {
pub fn link_syscall<Args, Ret>(
&mut self,
module: &'static str,
name: &'static str,
syscall: impl Syscall<K, Args, Ret>,
) -> anyhow::Result<&mut Self> {
syscall.link(self, module, name)?;
Ok(self)
}
}
pub trait Syscall<K, Args, Ret>: Send + Sync + 'static {
fn link(
self,
linker: &mut Linker<K>,
module: &'static str,
name: &'static str,
) -> anyhow::Result<()>;
}
pub enum ControlFlow<T> {
Return(T),
Error(SyscallError),
Abort(Abort),
}
impl<T> From<ExecutionError> for ControlFlow<T> {
fn from(value: ExecutionError) -> Self {
match value {
ExecutionError::Syscall(err) => ControlFlow::Error(err),
ExecutionError::Fatal(err) => ControlFlow::Abort(Abort::Fatal(err)),
ExecutionError::OutOfGas => ControlFlow::Abort(Abort::OutOfGas),
}
}
}
pub trait IntoControlFlow: Sized {
type Value: SyscallSafe;
fn into_control_flow(self) -> ControlFlow<Self::Value>;
}
#[derive(Copy, Clone)]
pub enum Never {}
unsafe impl SyscallSafe for Never {}
impl IntoControlFlow for Abort {
type Value = Never;
fn into_control_flow(self) -> ControlFlow<Self::Value> {
ControlFlow::Abort(self)
}
}
impl<T> IntoControlFlow for ControlFlow<T>
where
T: SyscallSafe,
{
type Value = T;
fn into_control_flow(self) -> ControlFlow<Self::Value> {
self
}
}
impl<T> IntoControlFlow for kernel::Result<T>
where
T: SyscallSafe,
{
type Value = T;
fn into_control_flow(self) -> ControlFlow<Self::Value> {
match self {
Ok(value) => ControlFlow::Return(value),
Err(e) => match e {
ExecutionError::Syscall(err) => ControlFlow::Error(err),
ExecutionError::OutOfGas => ControlFlow::Abort(Abort::OutOfGas),
ExecutionError::Fatal(err) => ControlFlow::Abort(Abort::Fatal(err)),
},
}
}
}
fn memory_and_data<'a, K: Kernel>(
caller: &'a mut Caller<'_, InvocationData<K>>,
) -> (&'a mut Memory, &'a mut InvocationData<K>) {
let memory_handle = caller.data().memory;
let (mem, data) = memory_handle.data_and_store_mut(caller);
(Memory::new(mem), data)
}
macro_rules! impl_syscall {
($($t:ident)*) => {
#[allow(non_snake_case)]
impl<$($t,)* Ret, K, Func> Syscall<K, ($($t,)*), Ret> for Func
where
K: Kernel,
Func: Fn(Context<'_, K> $(, $t)*) -> Ret + Send + Sync + 'static,
Ret: IntoControlFlow,
$($t: WasmTy+SyscallSafe,)*
{
fn link(
self,
linker: &mut Linker<K>,
module: &'static str,
name: &'static str,
) -> anyhow::Result<()> {
if mem::size_of::<Ret::Value>() == 0 {
linker.0.func_wrap(module, name, move |mut caller: Caller<'_, InvocationData<K>> $(, $t: $t)*| {
charge_for_exec(&mut caller)?;
charge_syscall_gas(&mut caller)?;
let (mut memory, data) = memory_and_data(&mut caller);
let ctx = Context{kernel: &mut data.kernel, memory: &mut memory};
let out = self(ctx $(, $t)*).into_control_flow();
let result = match out {
ControlFlow::Return(_) => {
log::trace!("syscall {}::{}: ok", module, name);
data.last_error = None;
Ok(0)
},
ControlFlow::Error(err) => {
let code = err.1;
log::trace!("syscall {}::{}: fail ({})", module, name, code as u32);
data.last_error = Some(backtrace::Cause::from_syscall(module, name, err));
Ok(code as u32)
},
ControlFlow::Abort(abort) => Err(abort.into()),
};
update_gas_available(&mut caller)?;
result
})?;
} else {
linker.0.func_wrap(module, name, move |mut caller: Caller<'_, InvocationData<K>>, ret: u32 $(, $t: $t)*| {
charge_for_exec(&mut caller)?;
charge_syscall_gas(&mut caller)?;
let (mut memory, data) = memory_and_data(&mut caller);
if (ret as u64) > (memory.len() as u64)
|| memory.len() - (ret as usize) < mem::size_of::<Ret::Value>() {
let code = ErrorNumber::IllegalArgument;
data.last_error = Some(backtrace::Cause::from_syscall(module, name, SyscallError(format!("no space for return value"), code)));
return Ok(code as u32);
}
let ctx = Context{kernel: &mut data.kernel, memory: &mut memory};
let result = match self(ctx $(, $t)*).into_control_flow() {
ControlFlow::Return(value) => {
log::trace!("syscall {}::{}: ok", module, name);
unsafe {
(memory.as_mut_ptr().offset(ret as isize) as *mut Ret::Value).write_unaligned(value);
}
data.last_error = None;
Ok(0)
},
ControlFlow::Error(err) => {
let code = err.1;
log::trace!("syscall {}::{}: fail ({})", module, name, code as u32);
data.last_error = Some(backtrace::Cause::from_syscall(module, name, err));
Ok(code as u32)
},
ControlFlow::Abort(abort) => Err(abort.into()),
};
update_gas_available(&mut caller)?;
result
})?;
}
Ok(())
}
}
}
}
impl_syscall!();
impl_syscall!(A);
impl_syscall!(A B);
impl_syscall!(A B C);
impl_syscall!(A B C D);
impl_syscall!(A B C D E);
impl_syscall!(A B C D E F);
impl_syscall!(A B C D E F G);
impl_syscall!(A B C D E F G H);